diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index 130d39fc80..f0743de6e3 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,214 +1,217 @@ SET(EXTPREFIX_qt "${EXTPREFIX}") if (WIN32) list(APPEND _QT_conf -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtenginio -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-ssl -no-openssl -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 # -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) + ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.9/5.9.3/single/qt-everywhere-opensource-src-5.9.3.zip URL_HASH SHA1=2d3c53cd9dc76a479873548921a20d3d9b6fb9ac PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/disable-wintab.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qtgui-private-headers.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Don-t-request-the-MIME-image-every-time-Windows-asks.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Hack-always-return-we-support-DIBV5.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Hack-for-fullscreen-workaround.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qopengldebug-gles.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/gerrit-189539-ANGLE-mingw-fix.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/gerrit-212811_qtbase-angle-d3d11-warp-crash-fix.patch 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 ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.6/5.6.1-1/single/qt-everywhere-opensource-src-5.6.1-1.tar.gz URL_MD5 8fdec6d657bc370bd3183d8fe8e9c47a PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qt-no-motion-compression.diff INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -nomake examples -no-sql-sqlite -no-openssl -no-qml-debug -no-mtdev -no-journald -no-syslog -no-nis -no-cups -no-tslib -no-directfb -no-linuxfb -no-libproxy -no-pch -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -qt-harfbuzz -qt-freetype -qt-xcb -qt-xkbcommon-x11 -optimized-qmake -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtenginio -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport 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 -i ${CMAKE_CURRENT_SOURCE_DIR}/gerrit-166202.diff - COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff - COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/qtbase-configure.patch - COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase/mkspecs/features/mac -i ${CMAKE_CURRENT_SOURCE_DIR}/mac-default.patch) + set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff + #COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/qtbase-configure.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 -i ${CMAKE_CURRENT_SOURCE_DIR}/gerrit-166202.diff - COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff) + set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff) 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 + BUILD_IN_SOURCE ON - URL https://download.qt.io/official_releases/qt/5.7/5.7.0/single/qt-everywhere-opensource-src-5.7.0.tar.gz - URL_MD5 9a46cce61fc64c20c3ac0a0e0fa41b42 + URL https://download.qt.io/development_releases/qt/5.10/5.10.0-beta3/single/qt-everywhere-src-5.10.0.tar.xz + URL_MD5 e809bf33732fb68d531e609979ba529f PATCH_COMMAND ${ext_qt_PATCH_COMMAND} INSTALL_DIR ${EXTPREFIX_qt} - CONFIGURE_COMMAND /configure -confirm-license -opensource -nomake examples -no-openssl -no-compile-examples -qt-freetype -qt-harfbuzz -opengl desktop -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -prefix ${EXTPREFIX_qt} + CONFIGURE_COMMAND /configure + -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtenginio -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland + -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -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 -prefix ${EXTPREFIX_qt} BUILD_COMMAND ${PARALLEL_MAKE} INSTALL_COMMAND make install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/3rdparty/ext_qt/gerrit-166202.diff b/3rdparty/ext_qt/gerrit-166202.diff deleted file mode 100644 index 233767ddaf..0000000000 --- a/3rdparty/ext_qt/gerrit-166202.diff +++ /dev/null @@ -1,1067 +0,0 @@ -diff --git a/qtbase/src/gui/opengl/qopenglengineshadermanager.cpp b/qtbase/src/gui/opengl/qopenglengineshadermanager.cpp -index 4e3d14b..c633236 100644 ---- a/qtbase/src/gui/opengl/qopenglengineshadermanager.cpp -+++ b/qtbase/src/gui/opengl/qopenglengineshadermanager.cpp -@@ -131,58 +131,116 @@ QOpenGLEngineSharedShaders::QOpenGLEngineSharedShaders(QOpenGLContext* context) - - const char** code = qShaderSnippets; // shortcut - -- code[MainVertexShader] = qopenglslMainVertexShader; -- code[MainWithTexCoordsVertexShader] = qopenglslMainWithTexCoordsVertexShader; -- code[MainWithTexCoordsAndOpacityVertexShader] = qopenglslMainWithTexCoordsAndOpacityVertexShader; -- -- code[UntransformedPositionVertexShader] = qopenglslUntransformedPositionVertexShader; -- code[PositionOnlyVertexShader] = qopenglslPositionOnlyVertexShader; -- code[ComplexGeometryPositionOnlyVertexShader] = qopenglslComplexGeometryPositionOnlyVertexShader; -- code[PositionWithPatternBrushVertexShader] = qopenglslPositionWithPatternBrushVertexShader; -- code[PositionWithLinearGradientBrushVertexShader] = qopenglslPositionWithLinearGradientBrushVertexShader; -- code[PositionWithConicalGradientBrushVertexShader] = qopenglslPositionWithConicalGradientBrushVertexShader; -- code[PositionWithRadialGradientBrushVertexShader] = qopenglslPositionWithRadialGradientBrushVertexShader; -- code[PositionWithTextureBrushVertexShader] = qopenglslPositionWithTextureBrushVertexShader; -- code[AffinePositionWithPatternBrushVertexShader] = qopenglslAffinePositionWithPatternBrushVertexShader; -- code[AffinePositionWithLinearGradientBrushVertexShader] = qopenglslAffinePositionWithLinearGradientBrushVertexShader; -- code[AffinePositionWithConicalGradientBrushVertexShader] = qopenglslAffinePositionWithConicalGradientBrushVertexShader; -- code[AffinePositionWithRadialGradientBrushVertexShader] = qopenglslAffinePositionWithRadialGradientBrushVertexShader; -- code[AffinePositionWithTextureBrushVertexShader] = qopenglslAffinePositionWithTextureBrushVertexShader; -- -- code[MainFragmentShader_CMO] = qopenglslMainFragmentShader_CMO; -- code[MainFragmentShader_CM] = qopenglslMainFragmentShader_CM; -- code[MainFragmentShader_MO] = qopenglslMainFragmentShader_MO; -- code[MainFragmentShader_M] = qopenglslMainFragmentShader_M; -- code[MainFragmentShader_CO] = qopenglslMainFragmentShader_CO; -- code[MainFragmentShader_C] = qopenglslMainFragmentShader_C; -- code[MainFragmentShader_O] = qopenglslMainFragmentShader_O; -- code[MainFragmentShader] = qopenglslMainFragmentShader; -- code[MainFragmentShader_ImageArrays] = qopenglslMainFragmentShader_ImageArrays; -- -- code[ImageSrcFragmentShader] = qopenglslImageSrcFragmentShader; -- code[ImageSrcWithPatternFragmentShader] = qopenglslImageSrcWithPatternFragmentShader; -- code[NonPremultipliedImageSrcFragmentShader] = qopenglslNonPremultipliedImageSrcFragmentShader; -- code[GrayscaleImageSrcFragmentShader] = qopenglslGrayscaleImageSrcFragmentShader; -- code[AlphaImageSrcFragmentShader] = qopenglslAlphaImageSrcFragmentShader; -- code[CustomImageSrcFragmentShader] = qopenglslCustomSrcFragmentShader; // Calls "customShader", which must be appended -- code[SolidBrushSrcFragmentShader] = qopenglslSolidBrushSrcFragmentShader; -- if (context->isOpenGLES()) -- code[TextureBrushSrcFragmentShader] = qopenglslTextureBrushSrcFragmentShader_ES; -- else -- code[TextureBrushSrcFragmentShader] = qopenglslTextureBrushSrcFragmentShader_desktop; -- code[TextureBrushSrcWithPatternFragmentShader] = qopenglslTextureBrushSrcWithPatternFragmentShader; -- code[PatternBrushSrcFragmentShader] = qopenglslPatternBrushSrcFragmentShader; -- code[LinearGradientBrushSrcFragmentShader] = qopenglslLinearGradientBrushSrcFragmentShader; -- code[RadialGradientBrushSrcFragmentShader] = qopenglslRadialGradientBrushSrcFragmentShader; -- code[ConicalGradientBrushSrcFragmentShader] = qopenglslConicalGradientBrushSrcFragmentShader; -- code[ShockingPinkSrcFragmentShader] = qopenglslShockingPinkSrcFragmentShader; -- -- code[NoMaskFragmentShader] = ""; -- code[MaskFragmentShader] = qopenglslMaskFragmentShader; -- code[RgbMaskFragmentShaderPass1] = qopenglslRgbMaskFragmentShaderPass1; -- code[RgbMaskFragmentShaderPass2] = qopenglslRgbMaskFragmentShaderPass2; -- code[RgbMaskWithGammaFragmentShader] = ""; //### -+ // Check if the user has requested an OpenGL 3.2 Core Profile or higher -+ // and if so use GLSL 1.5 core shaders instead of legacy ones. -+ const QSurfaceFormat &fmt = context->format(); -+ if (fmt.profile() == QSurfaceFormat::CoreProfile && fmt.version() >= qMakePair(3,2)) { -+ code[MainVertexShader] = qopenglslMainVertexShader_core; -+ code[MainWithTexCoordsVertexShader] = qopenglslMainWithTexCoordsVertexShader_core; -+ code[MainWithTexCoordsAndOpacityVertexShader] = qopenglslMainWithTexCoordsAndOpacityVertexShader_core; -+ -+ code[UntransformedPositionVertexShader] = qopenglslUntransformedPositionVertexShader_core; -+ code[PositionOnlyVertexShader] = qopenglslPositionOnlyVertexShader_core; -+ code[ComplexGeometryPositionOnlyVertexShader] = qopenglslComplexGeometryPositionOnlyVertexShader_core; -+ code[PositionWithPatternBrushVertexShader] = qopenglslPositionWithPatternBrushVertexShader_core; -+ code[PositionWithLinearGradientBrushVertexShader] = qopenglslPositionWithLinearGradientBrushVertexShader_core; -+ code[PositionWithConicalGradientBrushVertexShader] = qopenglslPositionWithConicalGradientBrushVertexShader_core; -+ code[PositionWithRadialGradientBrushVertexShader] = qopenglslPositionWithRadialGradientBrushVertexShader_core; -+ code[PositionWithTextureBrushVertexShader] = qopenglslPositionWithTextureBrushVertexShader_core; -+ code[AffinePositionWithPatternBrushVertexShader] = qopenglslAffinePositionWithPatternBrushVertexShader_core; -+ code[AffinePositionWithLinearGradientBrushVertexShader] = qopenglslAffinePositionWithLinearGradientBrushVertexShader_core; -+ code[AffinePositionWithConicalGradientBrushVertexShader] = qopenglslAffinePositionWithConicalGradientBrushVertexShader_core; -+ code[AffinePositionWithRadialGradientBrushVertexShader] = qopenglslAffinePositionWithRadialGradientBrushVertexShader_core; -+ code[AffinePositionWithTextureBrushVertexShader] = qopenglslAffinePositionWithTextureBrushVertexShader_core; -+ -+ code[MainFragmentShader_CMO] = qopenglslMainFragmentShader_CMO_core; -+ code[MainFragmentShader_CM] = qopenglslMainFragmentShader_CM_core; -+ code[MainFragmentShader_MO] = qopenglslMainFragmentShader_MO_core; -+ code[MainFragmentShader_M] = qopenglslMainFragmentShader_M_core; -+ code[MainFragmentShader_CO] = qopenglslMainFragmentShader_CO_core; -+ code[MainFragmentShader_C] = qopenglslMainFragmentShader_C_core; -+ code[MainFragmentShader_O] = qopenglslMainFragmentShader_O_core; -+ code[MainFragmentShader] = qopenglslMainFragmentShader_core; -+ code[MainFragmentShader_ImageArrays] = qopenglslMainFragmentShader_ImageArrays_core; -+ -+ code[ImageSrcFragmentShader] = qopenglslImageSrcFragmentShader_core; -+ code[ImageSrcWithPatternFragmentShader] = qopenglslImageSrcWithPatternFragmentShader_core; -+ code[NonPremultipliedImageSrcFragmentShader] = qopenglslNonPremultipliedImageSrcFragmentShader_core; -+ code[GrayscaleImageSrcFragmentShader] = qopenglslGrayscaleImageSrcFragmentShader_core; -+ code[AlphaImageSrcFragmentShader] = qopenglslAlphaImageSrcFragmentShader_core; -+ code[CustomImageSrcFragmentShader] = qopenglslCustomSrcFragmentShader_core; // Calls "customShader", which must be appended -+ code[SolidBrushSrcFragmentShader] = qopenglslSolidBrushSrcFragmentShader_core; -+ -+ code[TextureBrushSrcFragmentShader] = qopenglslTextureBrushSrcFragmentShader_desktop_core; -+ code[TextureBrushSrcWithPatternFragmentShader] = qopenglslTextureBrushSrcWithPatternFragmentShader_core; -+ code[PatternBrushSrcFragmentShader] = qopenglslPatternBrushSrcFragmentShader_core; -+ code[LinearGradientBrushSrcFragmentShader] = qopenglslLinearGradientBrushSrcFragmentShader_core; -+ code[RadialGradientBrushSrcFragmentShader] = qopenglslRadialGradientBrushSrcFragmentShader_core; -+ code[ConicalGradientBrushSrcFragmentShader] = qopenglslConicalGradientBrushSrcFragmentShader_core; -+ code[ShockingPinkSrcFragmentShader] = qopenglslShockingPinkSrcFragmentShader_core; -+ -+ code[NoMaskFragmentShader] = ""; -+ code[MaskFragmentShader] = qopenglslMaskFragmentShader_core; -+ code[RgbMaskFragmentShaderPass1] = qopenglslRgbMaskFragmentShaderPass1_core; -+ code[RgbMaskFragmentShaderPass2] = qopenglslRgbMaskFragmentShaderPass2_core; -+ code[RgbMaskWithGammaFragmentShader] = ""; //### -+ } else { -+ code[MainVertexShader] = qopenglslMainVertexShader; -+ code[MainWithTexCoordsVertexShader] = qopenglslMainWithTexCoordsVertexShader; -+ code[MainWithTexCoordsAndOpacityVertexShader] = qopenglslMainWithTexCoordsAndOpacityVertexShader; -+ -+ code[UntransformedPositionVertexShader] = qopenglslUntransformedPositionVertexShader; -+ code[PositionOnlyVertexShader] = qopenglslPositionOnlyVertexShader; -+ code[ComplexGeometryPositionOnlyVertexShader] = qopenglslComplexGeometryPositionOnlyVertexShader; -+ code[PositionWithPatternBrushVertexShader] = qopenglslPositionWithPatternBrushVertexShader; -+ code[PositionWithLinearGradientBrushVertexShader] = qopenglslPositionWithLinearGradientBrushVertexShader; -+ code[PositionWithConicalGradientBrushVertexShader] = qopenglslPositionWithConicalGradientBrushVertexShader; -+ code[PositionWithRadialGradientBrushVertexShader] = qopenglslPositionWithRadialGradientBrushVertexShader; -+ code[PositionWithTextureBrushVertexShader] = qopenglslPositionWithTextureBrushVertexShader; -+ code[AffinePositionWithPatternBrushVertexShader] = qopenglslAffinePositionWithPatternBrushVertexShader; -+ code[AffinePositionWithLinearGradientBrushVertexShader] = qopenglslAffinePositionWithLinearGradientBrushVertexShader; -+ code[AffinePositionWithConicalGradientBrushVertexShader] = qopenglslAffinePositionWithConicalGradientBrushVertexShader; -+ code[AffinePositionWithRadialGradientBrushVertexShader] = qopenglslAffinePositionWithRadialGradientBrushVertexShader; -+ code[AffinePositionWithTextureBrushVertexShader] = qopenglslAffinePositionWithTextureBrushVertexShader; -+ -+ code[MainFragmentShader_CMO] = qopenglslMainFragmentShader_CMO; -+ code[MainFragmentShader_CM] = qopenglslMainFragmentShader_CM; -+ code[MainFragmentShader_MO] = qopenglslMainFragmentShader_MO; -+ code[MainFragmentShader_M] = qopenglslMainFragmentShader_M; -+ code[MainFragmentShader_CO] = qopenglslMainFragmentShader_CO; -+ code[MainFragmentShader_C] = qopenglslMainFragmentShader_C; -+ code[MainFragmentShader_O] = qopenglslMainFragmentShader_O; -+ code[MainFragmentShader] = qopenglslMainFragmentShader; -+ code[MainFragmentShader_ImageArrays] = qopenglslMainFragmentShader_ImageArrays; -+ -+ code[ImageSrcFragmentShader] = qopenglslImageSrcFragmentShader; -+ code[ImageSrcWithPatternFragmentShader] = qopenglslImageSrcWithPatternFragmentShader; -+ code[NonPremultipliedImageSrcFragmentShader] = qopenglslNonPremultipliedImageSrcFragmentShader; -+ code[GrayscaleImageSrcFragmentShader] = qopenglslGrayscaleImageSrcFragmentShader; -+ code[AlphaImageSrcFragmentShader] = qopenglslAlphaImageSrcFragmentShader; -+ code[CustomImageSrcFragmentShader] = qopenglslCustomSrcFragmentShader; // Calls "customShader", which must be appended -+ code[SolidBrushSrcFragmentShader] = qopenglslSolidBrushSrcFragmentShader; -+ if (context->isOpenGLES()) -+ code[TextureBrushSrcFragmentShader] = qopenglslTextureBrushSrcFragmentShader_ES; -+ else -+ code[TextureBrushSrcFragmentShader] = qopenglslTextureBrushSrcFragmentShader_desktop; -+ code[TextureBrushSrcWithPatternFragmentShader] = qopenglslTextureBrushSrcWithPatternFragmentShader; -+ code[PatternBrushSrcFragmentShader] = qopenglslPatternBrushSrcFragmentShader; -+ code[LinearGradientBrushSrcFragmentShader] = qopenglslLinearGradientBrushSrcFragmentShader; -+ code[RadialGradientBrushSrcFragmentShader] = qopenglslRadialGradientBrushSrcFragmentShader; -+ code[ConicalGradientBrushSrcFragmentShader] = qopenglslConicalGradientBrushSrcFragmentShader; -+ code[ShockingPinkSrcFragmentShader] = qopenglslShockingPinkSrcFragmentShader; -+ -+ code[NoMaskFragmentShader] = ""; -+ code[MaskFragmentShader] = qopenglslMaskFragmentShader; -+ code[RgbMaskFragmentShaderPass1] = qopenglslRgbMaskFragmentShaderPass1; -+ code[RgbMaskFragmentShaderPass2] = qopenglslRgbMaskFragmentShaderPass2; -+ code[RgbMaskWithGammaFragmentShader] = ""; //### -+ } - -+ // These shaders are not implemented yet and therefore are the same -+ // for all profiles. Implementations should make a version for both -+ // profiles and put the appropriate lines in the if-statement above. - code[NoCompositionModeFragmentShader] = ""; - code[MultiplyCompositionModeFragmentShader] = ""; //### - code[ScreenCompositionModeFragmentShader] = ""; //### -diff --git a/qtbase/src/gui/opengl/qopenglengineshadersource_p.h b/qtbase/src/gui/opengl/qopenglengineshadersource_p.h -index 876d277..73aeb79 100644 ---- a/qtbase/src/gui/opengl/qopenglengineshadersource_p.h -+++ b/qtbase/src/gui/opengl/qopenglengineshadersource_p.h -@@ -56,8 +56,6 @@ - - QT_BEGIN_NAMESPACE - -- -- - static const char* const qopenglslMainVertexShader = "\n\ - void setPosition(); \n\ - void main(void) \n\ -@@ -531,40 +529,498 @@ static const char* const qopenglslRgbMaskFragmentShaderPass2 = "\n\ - ExclusionCompositionModeFragmentShader, - */ - --// OpenGL 3.2 core profile versions of shaders that are used by QOpenGLTextureGlyphCache -+/* -+ OpenGL 3.2+ Core Profile shaders -+ The following shader snippets are copies of the snippets above -+ but use the modern GLSL 1.5 keywords. New shaders should make -+ a snippet for both profiles and add them appropriately in the -+ shader manager. -+*/ -+static const char* const qopenglslMainVertexShader_core = -+ "#version 150 core\n\ -+ void setPosition(); \n\ -+ void main(void) \n\ -+ { \n\ -+ setPosition(); \n\ -+ }\n"; -+ -+static const char* const qopenglslMainWithTexCoordsVertexShader_core = -+ "#version 150 core\n\ -+ in vec2 textureCoordArray; \n\ -+ out vec2 textureCoords; \n\ -+ void setPosition(); \n\ -+ void main(void) \n\ -+ { \n\ -+ setPosition(); \n\ -+ textureCoords = textureCoordArray; \n\ -+ }\n"; - --static const char* const qopenglslMainWithTexCoordsVertexShader_core = "#version 150 core \n\ -- in vec2 textureCoordArray; \n\ -- out vec2 textureCoords; \n\ -- void setPosition(); \n\ -- void main(void) \n\ -- { \n\ -- setPosition(); \n\ -- textureCoords = textureCoordArray; \n\ -- }\n"; -+static const char* const qopenglslMainWithTexCoordsAndOpacityVertexShader_core = -+ "#version 150 core\n\ -+ in vec2 textureCoordArray; \n\ -+ in float opacityArray; \n\ -+ out vec2 textureCoords; \n\ -+ out float opacity; \n\ -+ void setPosition(); \n\ -+ void main(void) \n\ -+ { \n\ -+ setPosition(); \n\ -+ textureCoords = textureCoordArray; \n\ -+ opacity = opacityArray; \n\ -+ }\n"; -+ -+// NOTE: We let GL do the perspective correction so texture lookups in the fragment -+// shader are also perspective corrected. -+static const char* const qopenglslPositionOnlyVertexShader_core = "\n\ -+ in vec2 vertexCoordsArray; \n\ -+ in vec3 pmvMatrix1; \n\ -+ in vec3 pmvMatrix2; \n\ -+ in vec3 pmvMatrix3; \n\ -+ void setPosition(void) \n\ -+ { \n\ -+ mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ -+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ -+ gl_Position = vec4(transformedPos.xy, 0.0, transformedPos.z); \n\ -+ }\n"; -+ -+static const char* const qopenglslComplexGeometryPositionOnlyVertexShader_core = "\n\ -+ in vec2 vertexCoordsArray; \n\ -+ uniform mat3 matrix; \n\ -+ void setPosition(void) \n\ -+ { \n\ -+ gl_Position = vec4(matrix * vec3(vertexCoordsArray, 1), 1);\n\ -+ } \n"; - - static const char* const qopenglslUntransformedPositionVertexShader_core = "\n\ -- in vec4 vertexCoordsArray; \n\ -- void setPosition(void) \n\ -- { \n\ -- gl_Position = vertexCoordsArray; \n\ -- }\n"; -- --static const char* const qopenglslMainFragmentShader_core = "#version 150 core \n\ -- vec4 srcPixel(); \n\ -- out vec4 fragColor; \n\ -- void main() \n\ -- { \n\ -- fragColor = srcPixel(); \n\ -- }\n"; -+ in vec4 vertexCoordsArray; \n\ -+ void setPosition(void) \n\ -+ { \n\ -+ gl_Position = vertexCoordsArray; \n\ -+ }\n"; -+ -+// Pattern Brush - This assumes the texture size is 8x8 and thus, the inverted size is 0.125 -+static const char* const qopenglslPositionWithPatternBrushVertexShader_core = "\n\ -+ in vec2 vertexCoordsArray; \n\ -+ in vec3 pmvMatrix1; \n\ -+ in vec3 pmvMatrix2; \n\ -+ in vec3 pmvMatrix3; \n\ -+ out vec2 patternTexCoords; \n\ -+ uniform vec2 halfViewportSize; \n\ -+ uniform vec2 invertedTextureSize; \n\ -+ uniform mat3 brushTransform; \n\ -+ void setPosition(void) \n\ -+ { \n\ -+ mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ -+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ -+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ -+ vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ -+ vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1.0); \n\ -+ float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ -+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ -+ patternTexCoords.xy = (hTexCoords.xy * 0.125) * invertedHTexCoordsZ; \n\ -+ }\n"; -+ -+static const char* const qopenglslAffinePositionWithPatternBrushVertexShader_core -+ = qopenglslPositionWithPatternBrushVertexShader_core; -+ -+static const char* const qopenglslPatternBrushSrcFragmentShader_core = "\n\ -+ in vec2 patternTexCoords;\n\ -+ uniform sampler2D brushTexture; \n\ -+ uniform vec4 patternColor; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return patternColor * (1.0 - texture(brushTexture, patternTexCoords).r); \n\ -+ }\n"; -+ -+ -+// Linear Gradient Brush -+static const char* const qopenglslPositionWithLinearGradientBrushVertexShader_core = "\n\ -+ in vec2 vertexCoordsArray; \n\ -+ in vec3 pmvMatrix1; \n\ -+ in vec3 pmvMatrix2; \n\ -+ in vec3 pmvMatrix3; \n\ -+ out float index; \n\ -+ uniform vec2 halfViewportSize; \n\ -+ uniform vec3 linearData; \n\ -+ uniform mat3 brushTransform; \n\ -+ void setPosition() \n\ -+ { \n\ -+ mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ -+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ -+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ -+ vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ -+ vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\ -+ float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ -+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ -+ index = (dot(linearData.xy, hTexCoords.xy) * linearData.z) * invertedHTexCoordsZ; \n\ -+ }\n"; -+ -+static const char* const qopenglslAffinePositionWithLinearGradientBrushVertexShader_core -+ = qopenglslPositionWithLinearGradientBrushVertexShader_core; -+ -+static const char* const qopenglslLinearGradientBrushSrcFragmentShader_core = "\n\ -+ uniform sampler2D brushTexture; \n\ -+ in float index; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ vec2 val = vec2(index, 0.5); \n\ -+ return texture(brushTexture, val); \n\ -+ }\n"; -+ -+ -+// Conical Gradient Brush -+static const char* const qopenglslPositionWithConicalGradientBrushVertexShader_core = "\n\ -+ in vec2 vertexCoordsArray; \n\ -+ in vec3 pmvMatrix1; \n\ -+ in vec3 pmvMatrix2; \n\ -+ in vec3 pmvMatrix3; \n\ -+ out vec2 A; \n\ -+ uniform vec2 halfViewportSize; \n\ -+ uniform mat3 brushTransform; \n\ -+ void setPosition(void) \n\ -+ { \n\ -+ mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ -+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ -+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ -+ vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ -+ vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\ -+ float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ -+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ -+ A = hTexCoords.xy * invertedHTexCoordsZ; \n\ -+ }\n"; -+ -+static const char* const qopenglslAffinePositionWithConicalGradientBrushVertexShader_core -+ = qopenglslPositionWithConicalGradientBrushVertexShader_core; -+ -+static const char* const qopenglslConicalGradientBrushSrcFragmentShader_core = "\n\ -+ #define INVERSE_2PI 0.1591549430918953358 \n\ -+ in vec2 A; \n\ -+ uniform sampler2D brushTexture; \n\ -+ uniform float angle; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ float t; \n\ -+ if (abs(A.y) == abs(A.x)) \n\ -+ t = (atan(-A.y + 0.002, A.x) + angle) * INVERSE_2PI; \n\ -+ else \n\ -+ t = (atan(-A.y, A.x) + angle) * INVERSE_2PI; \n\ -+ return texture(brushTexture, vec2(t - floor(t), 0.5)); \n\ -+ }\n"; -+ -+ -+// Radial Gradient Brush -+static const char* const qopenglslPositionWithRadialGradientBrushVertexShader_core = "\n\ -+ in vec2 vertexCoordsArray;\n\ -+ in vec3 pmvMatrix1; \n\ -+ in vec3 pmvMatrix2; \n\ -+ in vec3 pmvMatrix3; \n\ -+ out float b; \n\ -+ out vec2 A; \n\ -+ uniform vec2 halfViewportSize; \n\ -+ uniform mat3 brushTransform; \n\ -+ uniform vec2 fmp; \n\ -+ uniform vec3 bradius; \n\ -+ void setPosition(void) \n\ -+ {\n\ -+ mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ -+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ -+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ -+ vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ -+ vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\ -+ float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ -+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ -+ A = hTexCoords.xy * invertedHTexCoordsZ; \n\ -+ b = bradius.x + 2.0 * dot(A, fmp); \n\ -+ }\n"; -+ -+static const char* const qopenglslAffinePositionWithRadialGradientBrushVertexShader_core -+ = qopenglslPositionWithRadialGradientBrushVertexShader_core; -+ -+static const char* const qopenglslRadialGradientBrushSrcFragmentShader_core = "\n\ -+ in float b; \n\ -+ in vec2 A; \n\ -+ uniform sampler2D brushTexture; \n\ -+ uniform float fmp2_m_radius2; \n\ -+ uniform float inverse_2_fmp2_m_radius2; \n\ -+ uniform float sqrfr; \n\ -+ uniform vec3 bradius; \n\ -+ \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ float c = sqrfr-dot(A, A); \n\ -+ float det = b*b - 4.0*fmp2_m_radius2*c; \n\ -+ vec4 result = vec4(0.0); \n\ -+ if (det >= 0.0) { \n\ -+ float detSqrt = sqrt(det); \n\ -+ float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2); \n\ -+ if (bradius.y + w * bradius.z >= 0.0) \n\ -+ result = texture(brushTexture, vec2(w, 0.5)); \n\ -+ } \n\ -+ return result; \n\ -+ }\n"; -+ -+ -+// Texture Brush -+static const char* const qopenglslPositionWithTextureBrushVertexShader_core = "\n\ -+ in vec2 vertexCoordsArray; \n\ -+ in vec3 pmvMatrix1; \n\ -+ in vec3 pmvMatrix2; \n\ -+ in vec3 pmvMatrix3; \n\ -+ out vec2 brushTextureCoords; \n\ -+ uniform vec2 halfViewportSize; \n\ -+ uniform vec2 invertedTextureSize; \n\ -+ uniform mat3 brushTransform; \n\ -+ \n\ -+ void setPosition(void) \n\ -+ { \n\ -+ mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ -+ vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ -+ gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ -+ vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ -+ vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\ -+ float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ -+ gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ -+ brushTextureCoords.xy = (hTexCoords.xy * invertedTextureSize) * gl_Position.w; \n\ -+ }\n"; -+ -+static const char* const qopenglslAffinePositionWithTextureBrushVertexShader_core -+ = qopenglslPositionWithTextureBrushVertexShader_core; -+ -+static const char* const qopenglslTextureBrushSrcFragmentShader_desktop_core = "\n\ -+ in vec2 brushTextureCoords; \n\ -+ uniform sampler2D brushTexture; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return texture(brushTexture, brushTextureCoords); \n\ -+ }\n"; -+ -+static const char* const qopenglslTextureBrushSrcWithPatternFragmentShader_core = "\n\ -+ in vec2 brushTextureCoords; \n\ -+ uniform vec4 patternColor; \n\ -+ uniform sampler2D brushTexture; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return patternColor * (1.0 - texture(brushTexture, brushTextureCoords).r); \n\ -+ }\n"; -+ -+// Solid Fill Brush -+static const char* const qopenglslSolidBrushSrcFragmentShader_core = "\n\ -+ uniform vec4 fragmentColor; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return fragmentColor; \n\ -+ }\n"; - - static const char* const qopenglslImageSrcFragmentShader_core = "\n\ -- in vec2 textureCoords; \n\ -- uniform sampler2D imageTexture; \n\ -- vec4 srcPixel() \n\ -- { \n" -- "return texture(imageTexture, textureCoords); \n" -- "}\n"; -+ in vec2 textureCoords; \n\ -+ uniform sampler2D imageTexture; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return texture(imageTexture, textureCoords); \n\ -+ }\n"; -+ -+static const char* const qopenglslCustomSrcFragmentShader_core = "\n\ -+ in vec2 textureCoords; \n\ -+ uniform sampler2D imageTexture; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return customShader(imageTexture, textureCoords); \n\ -+ }\n"; -+ -+static const char* const qopenglslImageSrcWithPatternFragmentShader_core = "\n\ -+ in vec2 textureCoords; \n\ -+ uniform vec4 patternColor; \n\ -+ uniform sampler2D imageTexture; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return patternColor * (1.0 - texture(imageTexture, textureCoords).r); \n\ -+ }\n"; -+ -+static const char* const qopenglslNonPremultipliedImageSrcFragmentShader_core = "\n\ -+ in vec2 textureCoords; \n\ -+ uniform sampler2D imageTexture; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ vec4 sample = texture(imageTexture, textureCoords); \n\ -+ sample.rgb = sample.rgb * sample.a; \n\ -+ return sample; \n\ -+ }\n"; -+ -+static const char* const qopenglslGrayscaleImageSrcFragmentShader_core = "\n\ -+ in vec2 textureCoords; \n\ -+ uniform sampler2D imageTexture; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return texture(imageTexture, textureCoords).rrra; \n\ -+ }\n"; -+ -+static const char* const qopenglslAlphaImageSrcFragmentShader_core = "\n\ -+ in vec2 textureCoords; \n\ -+ uniform sampler2D imageTexture; \n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return vec4(0, 0, 0, texture(imageTexture, textureCoords).r); \n\ -+ }\n"; -+ -+static const char* const qopenglslShockingPinkSrcFragmentShader_core = "\n\ -+ vec4 srcPixel() \n\ -+ { \n\ -+ return vec4(0.98, 0.06, 0.75, 1.0); \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_ImageArrays_core = -+ "#version 150 core\n\ -+ in float opacity; \n\ -+ out vec4 fragColor; \n\ -+ vec4 srcPixel(); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = srcPixel() * opacity; \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_CMO_core = -+ "#version 150 core\n\ -+ out vec4 fragColor; \n\ -+ uniform float globalOpacity; \n\ -+ vec4 srcPixel(); \n\ -+ vec4 applyMask(vec4); \n\ -+ vec4 compose(vec4); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = applyMask(compose(srcPixel()*globalOpacity))); \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_CM_core = -+ "#version 150 core\n\ -+ out vec4 fragColor; \n\ -+ vec4 srcPixel(); \n\ -+ vec4 applyMask(vec4); \n\ -+ vec4 compose(vec4); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = applyMask(compose(srcPixel())); \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_MO_core = -+ "#version 150 core\n\ -+ out vec4 fragColor; \n\ -+ uniform float globalOpacity; \n\ -+ vec4 srcPixel(); \n\ -+ vec4 applyMask(vec4); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = applyMask(srcPixel()*globalOpacity); \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_M_core = -+ "#version 150 core\n\ -+ out vec4 fragColor; \n\ -+ vec4 srcPixel(); \n\ -+ vec4 applyMask(vec4); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = applyMask(srcPixel()); \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_CO_core = -+ "#version 150 core\n\ -+ out vec4 fragColor; \n\ -+ uniform float globalOpacity; \n\ -+ vec4 srcPixel(); \n\ -+ vec4 compose(vec4); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = compose(srcPixel()*globalOpacity); \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_C_core = -+ "#version 150 core\n\ -+ out vec4 fragColor; \n\ -+ vec4 srcPixel(); \n\ -+ vec4 compose(vec4); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = compose(srcPixel()); \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_O_core = -+ "#version 150 core\n\ -+ out vec4 fragColor; \n\ -+ uniform float globalOpacity; \n\ -+ vec4 srcPixel(); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = srcPixel()*globalOpacity; \n\ -+ }\n"; -+ -+static const char* const qopenglslMainFragmentShader_core = -+ "#version 150 core\n\ -+ out vec4 fragColor; \n\ -+ vec4 srcPixel(); \n\ -+ void main() \n\ -+ { \n\ -+ fragColor = srcPixel(); \n\ -+ }\n"; -+ -+static const char* const qopenglslMaskFragmentShader_core = "\n\ -+ in vec2 textureCoords;\n\ -+ uniform sampler2D maskTexture;\n\ -+ vec4 applyMask(vec4 src) \n\ -+ {\n\ -+ vec4 mask = texture(maskTexture, textureCoords); \n\ -+ return src * mask.a; \n\ -+ }\n"; -+ -+// For source over with subpixel antialiasing, the final color is calculated per component as follows -+// (.a is alpha component, .c is red, green or blue component): -+// alpha = src.a * mask.c * opacity -+// dest.c = dest.c * (1 - alpha) + src.c * alpha -+// -+// In the first pass, calculate: dest.c = dest.c * (1 - alpha) with blend funcs: zero, 1 - source color -+// In the second pass, calculate: dest.c = dest.c + src.c * alpha with blend funcs: one, one -+// -+// If source is a solid color (src is constant), only the first pass is needed, with blend funcs: constant, 1 - source color -+ -+// For source composition with subpixel antialiasing, the final color is calculated per component as follows: -+// alpha = src.a * mask.c * opacity -+// dest.c = dest.c * (1 - mask.c) + src.c * alpha -+// -+ -+static const char* const qopenglslRgbMaskFragmentShaderPass1_core = "\n\ -+ in vec2 textureCoords;\n\ -+ uniform sampler2D maskTexture;\n\ -+ vec4 applyMask(vec4 src) \n\ -+ { \n\ -+ vec4 mask = texture(maskTexture, textureCoords); \n\ -+ return src.a * mask; \n\ -+ }\n"; -+ -+static const char* const qopenglslRgbMaskFragmentShaderPass2_core = "\n\ -+ in vec2 textureCoords;\n\ -+ uniform sampler2D maskTexture;\n\ -+ vec4 applyMask(vec4 src) \n\ -+ { \n\ -+ vec4 mask = texture(maskTexture, textureCoords); \n\ -+ return src * mask; \n\ -+ }\n"; -+ -+/* -+ Left to implement: -+ RgbMaskFragmentShader_core, -+ RgbMaskWithGammaFragmentShader_core, -+ -+ MultiplyCompositionModeFragmentShader_core, -+ ScreenCompositionModeFragmentShader_core, -+ OverlayCompositionModeFragmentShader_core, -+ DarkenCompositionModeFragmentShader_core, -+ LightenCompositionModeFragmentShader_core, -+ ColorDodgeCompositionModeFragmentShader_core, -+ ColorBurnCompositionModeFragmentShader_core, -+ HardLightCompositionModeFragmentShader_core, -+ SoftLightCompositionModeFragmentShader_core, -+ DifferenceCompositionModeFragmentShader_core, -+ ExclusionCompositionModeFragmentShader_core, -+*/ - - QT_END_NAMESPACE - -diff --git a/qtbase/src/gui/opengl/qopenglpaintengine.cpp b/qtbase/src/gui/opengl/qopenglpaintengine.cpp -index d93871c..4cc4218 100644 ---- a/qtbase/src/gui/opengl/qopenglpaintengine.cpp -+++ b/qtbase/src/gui/opengl/qopenglpaintengine.cpp -@@ -99,6 +99,10 @@ QOpenGL2PaintEngineExPrivate::~QOpenGL2PaintEngineExPrivate() - { - delete shaderManager; - -+ vertexBuffer.destroy(); -+ texCoordBuffer.destroy(); -+ vao.destroy(); -+ - if (elementIndicesVBOId != 0) { - funcs.glDeleteBuffers(1, &elementIndicesVBOId); - elementIndicesVBOId = 0; -@@ -578,6 +582,12 @@ void QOpenGL2PaintEngineExPrivate::drawTexture(const QOpenGLRect& dest, const QO - setCoords(staticVertexCoordinateArray, dest); - setCoords(staticTextureCoordinateArray, srcTextureRect); - -+ setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, true); -+ setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, true); -+ -+ uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8); -+ uploadData(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray, 8); -+ - funcs.glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - } - -@@ -664,6 +674,8 @@ void QOpenGL2PaintEngineExPrivate::resetGLState() - float color[] = { 1.0f, 1.0f, 1.0f, 1.0f }; - funcs.glVertexAttrib4fv(3, color); - } -+ if (vao.isCreated()) -+ vao.release(); - } - - void QOpenGL2PaintEngineEx::endNativePainting() -@@ -696,16 +708,16 @@ void QOpenGL2PaintEngineExPrivate::transferMode(EngineMode newMode) - } - - if (newMode == ImageDrawingMode) { -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray); -- setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray); -+ uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8); -+ uploadData(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray, 8); - } - - if (newMode == ImageArrayDrawingMode || newMode == ImageOpacityArrayDrawingMode) { -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinateArray.data()); -- setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinateArray.data()); -+ uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinateArray.data(), vertexCoordinateArray.vertexCount() * 2); -+ uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinateArray.data(), textureCoordinateArray.vertexCount() * 2); - - if (newMode == ImageOpacityArrayDrawingMode) -- setVertexAttributePointer(QT_OPACITY_ATTR, (GLfloat*)opacityArray.data()); -+ uploadData(QT_OPACITY_ATTR, (GLfloat*)opacityArray.data(), opacityArray.size()); - } - - // This needs to change when we implement high-quality anti-aliasing... -@@ -824,9 +836,10 @@ void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path) - prepareForDraw(currentBrush.isOpaque()); - #ifdef QT_OPENGL_CACHE_AS_VBOS - funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo); -+ uploadData(QT_VERTEX_COORD_ATTR, 0, cache->vertexCount); - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0); - #else -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, cache->vertices); -+ uploadData(QT_VERTEX_COORDS_ATTR, cache->vertices, cache->vertexCount * 2); - #endif - funcs.glDrawArrays(cache->primitiveType, 0, cache->vertexCount); - -@@ -920,6 +933,7 @@ void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path) - #ifdef QT_OPENGL_CACHE_AS_VBOS - funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo); - funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache->ibo); -+ uploadData(QT_VERTEX_COORDS_ATTR, 0, cache->vertexCount); - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0); - if (cache->indexType == QVertexIndexVector::UnsignedInt) - funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_INT, 0); -@@ -928,7 +942,7 @@ void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path) - funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - funcs.glBindBuffer(GL_ARRAY_BUFFER, 0); - #else -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, cache->vertices); -+ uploadData(QT_VERTEX_COORDS_ATTR, cache->vertices, cache->vertexCount * 2); - if (cache->indexType == QVertexIndexVector::UnsignedInt) - funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_INT, (qint32 *)cache->indices); - else -@@ -957,7 +971,7 @@ void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path) - vertices[i] = float(inverseScale * polys.vertices.at(i)); - - prepareForDraw(currentBrush.isOpaque()); -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, vertices.constData()); -+ uploadData(QT_VERTEX_COORDS_ATTR, vertices.constData(), vertices.size()); - if (funcs.hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint)) - funcs.glDrawElements(GL_TRIANGLES, polys.indices.size(), GL_UNSIGNED_INT, polys.indices.data()); - else -@@ -1075,7 +1089,6 @@ void QOpenGL2PaintEngineExPrivate::fillStencilWithVertexArray(const float *data, - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data); - funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, count); - #else -- - funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - if (q->state()->clipTestEnabled) { - funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip | GL_STENCIL_HIGH_BIT, -@@ -1083,7 +1096,8 @@ void QOpenGL2PaintEngineExPrivate::fillStencilWithVertexArray(const float *data, - } else { - funcs.glStencilFunc(GL_ALWAYS, GL_STENCIL_HIGH_BIT, 0xff); - } -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data); -+ -+ uploadData(QT_VERTEX_COORDS_ATTR, data, count * 2); - funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, count); - #endif - } -@@ -1213,7 +1227,8 @@ bool QOpenGL2PaintEngineExPrivate::prepareForDraw(bool srcPixelsAreOpaque) - void QOpenGL2PaintEngineExPrivate::composite(const QOpenGLRect& boundingRect) - { - setCoords(staticVertexCoordinateArray, boundingRect); -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray); -+ -+ uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8); - funcs.glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - } - -@@ -1222,16 +1237,12 @@ void QOpenGL2PaintEngineExPrivate::drawVertexArrays(const float *data, int *stop - GLenum primitive) - { - // Now setup the pointer to the vertex array: -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data); -+ uploadData(QT_VERTEX_COORDS_ATTR, data, stops[stopCount-1] * 2); - - int previousStop = 0; - for (int i=0; i %d:", previousStop, stop-1); -- for (int i=previousStop; isetBrush(penBrush); - d->stroke(path, pen); -@@ -1320,17 +1331,12 @@ void QOpenGL2PaintEngineExPrivate::stroke(const QVectorPath &path, const QPen &p - - if (!stroker.vertexCount()) - return; -- -+ funcs.glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR); - if (opaque) { - prepareForDraw(opaque); -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, stroker.vertices()); -- funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, stroker.vertexCount() / 2); -- --// QBrush b(Qt::green); --// d->setBrush(&b); --// d->prepareForDraw(true); --// glDrawArrays(GL_LINE_STRIP, 0, d->stroker.vertexCount() / 2); - -+ uploadData(QT_VERTEX_COORDS_ATTR, stroker.vertices(), stroker.vertexCount()); -+ funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, stroker.vertexCount() / 2); - } else { - qreal width = qpen_widthf(pen) / 2; - if (width == 0) -@@ -1839,8 +1845,8 @@ void QOpenGL2PaintEngineExPrivate::drawCachedGlyphs(QFontEngine::GlyphFormat gly - } - - if (glyphFormat != QFontEngine::Format_ARGB || recreateVertexArrays) { -- setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinates->data()); -- setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinates->data()); -+ uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinates->data(), vertexCoordinates->vertexCount() * 2); -+ uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinates->data(), textureCoordinates->vertexCount() * 2); - } - - if (!snapToPixelGrid) { -@@ -2081,6 +2087,29 @@ bool QOpenGL2PaintEngineEx::begin(QPaintDevice *pdev) - - d->funcs.initializeOpenGLFunctions(); - -+ // Generate a new Vertex Array Object if we don't have one already -+ if (!d->vao.isCreated()) { -+ bool created = d->vao.create(); -+ -+ // If we managed to create it then we have a profile that supports VAOs -+ if (created) { -+ d->vao.bind(); -+ -+ // Generate a new Vertex Buffer Object if we don't have one already -+ if (!d->vertexBuffer.isCreated()) { -+ d->vertexBuffer.create(); -+ // Set its usage to StreamDraw, we will use this buffer only a few times before refilling it -+ d->vertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); -+ } -+ // Generate a new Texture Buffer Object if we don't have one already -+ if (!d->texCoordBuffer.isCreated()) { -+ d->texCoordBuffer.create(); -+ // Set its usage to StreamDraw, we will use this buffer only a few times before refilling it -+ d->texCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); -+ } -+ } -+ } -+ - for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i) - d->vertexAttributeArraysEnabledState[i] = false; - -@@ -2162,6 +2191,10 @@ void QOpenGL2PaintEngineEx::ensureActive() - Q_D(QOpenGL2PaintEngineEx); - QOpenGLContext *ctx = d->ctx; - -+ if (d->vao.isCreated()) { -+ d->vao.bind(); -+ } -+ - if (isActive() && ctx->d_func()->active_engine != this) { - ctx->d_func()->active_engine = this; - d->needsSync = true; -diff --git a/qtbase/src/gui/opengl/qopenglpaintengine_p.h b/qtbase/src/gui/opengl/qopenglpaintengine_p.h -index c9f3282..fe63747 100644 ---- a/qtbase/src/gui/opengl/qopenglpaintengine_p.h -+++ b/qtbase/src/gui/opengl/qopenglpaintengine_p.h -@@ -64,6 +64,9 @@ - - #include - -+#include -+#include -+ - enum EngineMode { - ImageDrawingMode, - TextDrawingMode, -@@ -192,7 +195,9 @@ public: - snapToPixelGrid(false), - nativePaintingActive(false), - inverseScale(1), -- lastTextureUnitUsed(QT_UNKNOWN_TEXTURE_UNIT) -+ lastTextureUnitUsed(QT_UNKNOWN_TEXTURE_UNIT), -+ vertexBuffer(QOpenGLBuffer::VertexBuffer), -+ texCoordBuffer(QOpenGLBuffer::VertexBuffer) - { } - - ~QOpenGL2PaintEngineExPrivate(); -@@ -221,7 +226,7 @@ public: - void drawCachedGlyphs(QFontEngine::GlyphFormat glyphFormat, QStaticTextItem *staticTextItem); - - // Calls glVertexAttributePointer if the pointer has changed -- inline void setVertexAttributePointer(unsigned int arrayIndex, const GLfloat *pointer); -+ inline void uploadData(unsigned int arrayIndex, const GLfloat *data, const GLuint count); - - // draws whatever is in the vertex array: - void drawVertexArrays(const float *data, int *stops, int stopCount, GLenum primitive); -@@ -312,6 +317,10 @@ public: - GLenum lastTextureUnitUsed; - GLuint lastTextureUsed; - -+ QOpenGLVertexArrayObject vao; -+ QOpenGLBuffer vertexBuffer; -+ QOpenGLBuffer texCoordBuffer; -+ - bool needsSync; - bool multisamplingAlwaysEnabled; - -@@ -325,17 +334,38 @@ public: - }; - - --void QOpenGL2PaintEngineExPrivate::setVertexAttributePointer(unsigned int arrayIndex, const GLfloat *pointer) -+void QOpenGL2PaintEngineExPrivate::uploadData(unsigned int arrayIndex, const GLfloat *data, const GLuint count) - { - Q_ASSERT(arrayIndex < 3); -- if (pointer == vertexAttribPointers[arrayIndex]) -- return; -- -- vertexAttribPointers[arrayIndex] = pointer; -- if (arrayIndex == QT_OPACITY_ATTR) -- funcs.glVertexAttribPointer(arrayIndex, 1, GL_FLOAT, GL_FALSE, 0, pointer); -- else -- funcs.glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, pointer); -+ -+ // If a vertex array object is created we have a profile that supports them -+ // and we will upload the data via a QOpenGLBuffer. Otherwise we will use -+ // the legacy way of uploading the data via glVertexAttribPointer. -+ if (vao.isCreated()) { -+ if (arrayIndex == QT_VERTEX_COORDS_ATTR) { -+ vertexBuffer.bind(); -+ vertexBuffer.allocate(data, count * sizeof(float)); -+ } -+ if (arrayIndex == QT_TEXTURE_COORDS_ATTR) { -+ texCoordBuffer.bind(); -+ texCoordBuffer.allocate(data, count * sizeof(float)); -+ } -+ if (arrayIndex == QT_OPACITY_ATTR) -+ funcs.glVertexAttribPointer(arrayIndex, 1, GL_FLOAT, GL_FALSE, 0, 0); -+ else -+ funcs.glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0); -+ } else { -+ // If we already uploaded the data we don't have to do it again -+ if (data == vertexAttribPointers[arrayIndex]) -+ return; -+ -+ // Store the data in cache and upload it to the graphics card. -+ vertexAttribPointers[arrayIndex] = data; -+ if (arrayIndex == QT_OPACITY_ATTR) -+ funcs.glVertexAttribPointer(arrayIndex, 1, GL_FLOAT, GL_FALSE, 0, data); -+ else -+ funcs.glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, data); -+ } - } - - QT_END_NAMESPACE -diff --git a/qtbase/src/gui/opengl/qopengltextureglyphcache.cpp b/qtbase/src/gui/opengl/qopengltextureglyphcache.cpp -index 9a7b1eb..3a03989 100644 ---- a/qtbase/src/gui/opengl/qopengltextureglyphcache.cpp -+++ b/qtbase/src/gui/opengl/qopengltextureglyphcache.cpp -@@ -380,8 +380,8 @@ void QOpenGLTextureGlyphCache::resizeTextureData(int width, int height) - blitProgram = m_blitProgram; - - } else { -- pex->setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, m_vertexCoordinateArray); -- pex->setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, m_textureCoordinateArray); -+ pex->uploadData(QT_VERTEX_COORDS_ATTR, m_vertexCoordinateArray, 8); -+ pex->uploadData(QT_TEXTURE_COORDS_ATTR, m_textureCoordinateArray, 8); - - pex->shaderManager->useBlitProgram(); - blitProgram = pex->shaderManager->blitProgram(); -diff --git a/qtbase/src/gui/opengl/qtriangulatingstroker.cpp b/qtbase/src/gui/opengl/qtriangulatingstroker.cpp -index d9a3231..a7ce6c8 100644 ---- a/qtbase/src/gui/opengl/qtriangulatingstroker.cpp -+++ b/qtbase/src/gui/opengl/qtriangulatingstroker.cpp -@@ -261,7 +261,7 @@ void QTriangulatingStroker::moveTo(const qreal *pts) - normalVector(m_cx, m_cy, x2, y2, &m_nvx, &m_nvy); - - -- // To acheive jumps we insert zero-area tringles. This is done by -+ // To achieve jumps we insert zero-area triangles. This is done by - // adding two identical points in both the end of previous strip - // and beginning of next strip - bool invisibleJump = m_vertices.size(); diff --git a/3rdparty/ext_qt/macdeploy-qt.diff b/3rdparty/ext_qt/macdeploy-qt.diff index 0840194026..f871cff882 100644 --- a/3rdparty/ext_qt/macdeploy-qt.diff +++ b/3rdparty/ext_qt/macdeploy-qt.diff @@ -1,128 +1,134 @@ +commit 26d6c76d5a51504ebabec5f4ea2643069743f962 +Author: Boudewijn Rempt +Date: Sat Nov 4 14:15:25 2017 +0100 + + Fix macdeployqt + diff --git a/qttools/src/macdeployqt/macdeployqt/main.cpp b/qttools/src/macdeployqt/macdeployqt/main.cpp -index 2e6ad0c..8a90c1a 100644 +index 5488a5f..1e90c72 100644 --- a/qttools/src/macdeployqt/macdeployqt/main.cpp +++ b/qttools/src/macdeployqt/macdeployqt/main.cpp -@@ -52,6 +52,7 @@ int main(int argc, char **argv) - qDebug() << " -always-overwrite : Copy files even if the target file exists"; +@@ -53,6 +53,7 @@ int main(int argc, char **argv) qDebug() << " -codesign= : Run codesign with the given identity on all executables"; qDebug() << " -appstore-compliant: Skip deployment of components that use private API"; + qDebug() << " -libpath= : Add the given path to the library search path"; + qDebug() << " -extra-plugins= : Deploy plugins from given extra directory"; qDebug() << ""; qDebug() << "macdeployqt takes an application bundle as input and makes it"; qDebug() << "self-contained by copying in the Qt frameworks and plugins that"; -@@ -92,6 +93,7 @@ int main(int argc, char **argv) +@@ -94,6 +95,7 @@ int main(int argc, char **argv) extern QString codesignIdentiy; extern bool appstoreCompliant; extern bool deployFramework; + QStringList extraPluginDirectories; for (int i = 2; i < argc; ++i) { QByteArray argument = QByteArray(argv[i]); -@@ -153,6 +155,14 @@ int main(int argc, char **argv) +@@ -162,6 +164,14 @@ int main(int argc, char **argv) LogDebug() << "Argument found:" << argument; deployFramework = true; + } else if (argument.startsWith(QByteArray("-extra-plugins"))) { + LogDebug() << "Argument found:" << argument; + int index = argument.indexOf('='); + if (index == -1) + LogError() << "Missing extra plugins directory"; + else + extraPluginDirectories << argument.mid(index+1); + } else if (argument.startsWith("-")) { LogError() << "Unknown argument" << argument << "\n"; return 1; -@@ -183,10 +193,13 @@ int main(int argc, char **argv) +@@ -192,10 +202,13 @@ int main(int argc, char **argv) deploymentInfo.deployedFrameworks = deploymentInfo.deployedFrameworks.toSet().toList(); } - if (plugins && !deploymentInfo.qtPath.isEmpty()) { + if ((plugins && !deploymentInfo.qtPath.isEmpty()) || !extraPluginDirectories.isEmpty()) { deploymentInfo.pluginPath = deploymentInfo.qtPath + "/plugins"; LogNormal(); - deployPlugins(appBundlePath, deploymentInfo, useDebugLibs); + if (plugins && !deploymentInfo.qtPath.isEmpty()) + deployPlugins(appBundlePath, deploymentInfo, useDebugLibs); + if (!extraPluginDirectories.isEmpty()) + deployExtraPlugins(appBundlePath, deploymentInfo, useDebugLibs, extraPluginDirectories); createQtConf(appBundlePath); } diff --git a/qttools/src/macdeployqt/shared/shared.cpp b/qttools/src/macdeployqt/shared/shared.cpp -index 5577265..a590039 100644 +index 9575090..477f7a0 100644 --- a/qttools/src/macdeployqt/shared/shared.cpp +++ b/qttools/src/macdeployqt/shared/shared.cpp -@@ -1070,6 +1070,43 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl +@@ -1120,6 +1120,43 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl } } +void deployExtraPlugins(const ApplicationBundleInfo &appBundleInfo, + const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs, const QStringList &extraPluginDirectories) +{ + foreach (const QString &extraPluginDir, extraPluginDirectories) { + LogNormal() << "Deploying plugins from" << extraPluginDir; + + // search for dylib and so files, both work as plugins on mac os + QDir dir(extraPluginDir); + dir.setFilter(QDir::Files); + dir.setNameFilters(QStringList() << "*.dylib" << "*.so"); + QDirIterator dirIterator(dir, QDirIterator::Subdirectories); + QStringList pluginList; + while (dirIterator.hasNext()) { + dirIterator.next(); + if (!QFileInfo(dirIterator.filePath()).isFile()) + continue; + pluginList.append(dir.relativeFilePath(dirIterator.filePath())); + } + + // deploy all found plugins + foreach (const QString &plugin, pluginList) { + QString sourcePath = extraPluginDir + "/" + plugin; + const QString destinationPath = pluginDestinationPath + "/" + plugin; + QDir dir; + dir.mkpath(QFileInfo(destinationPath).path()); + + if (copyFilePrintStatus(sourcePath, destinationPath)) { + runStrip(destinationPath); + + QList frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs); + deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath); + + } + } + } +} + void createQtConf(const QString &appBundlePath) { // Set Plugins and imports paths. These are relative to App.app/Contents. -@@ -1111,6 +1148,16 @@ void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, +@@ -1161,6 +1198,16 @@ void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs); } +void deployExtraPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs, const QStringList &extraPluginDirectories) +{ + ApplicationBundleInfo applicationBundle; + applicationBundle.path = appBundlePath; + applicationBundle.binaryPath = findAppBinary(appBundlePath); + + const QString pluginDestinationPath = appBundlePath + "/" + "Contents/PlugIns"; + deployExtraPlugins(applicationBundle, pluginDestinationPath, deploymentInfo, useDebugLibs, extraPluginDirectories); +} + void deployQmlImport(const QString &appBundlePath, const QSet &rpaths, const QString &importSourcePath, const QString &importName) { QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName; diff --git a/qttools/src/macdeployqt/shared/shared.h b/qttools/src/macdeployqt/shared/shared.h index c173846..cceac3a 100644 --- a/qttools/src/macdeployqt/shared/shared.h +++ b/qttools/src/macdeployqt/shared/shared.h @@ -116,6 +116,7 @@ DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringLis DeploymentInfo deployQtFrameworks(QList frameworks,const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath); void createQtConf(const QString &appBundlePath); void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs); +void deployExtraPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs, const QStringList &extraPluginDirectories); bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs); void changeIdentification(const QString &id, const QString &binaryPath); void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath); diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a120aa28b..f770015fb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,687 +1,700 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.6.0) option(OVERRIDE_QT_VERSION "Use this to make it possible to build with Qt < 5.6.0. There will be bugs." OFF) if (OVERRIDE_QT_VERSION) set(MIN_QT_VERSION 5.4.0) endif() set(MIN_FRAMEWORKS_VERSION 5.7.0) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (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.9 -Wno-macro-redefined -Wno-deprecated-register) endif() if (LINUX) if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WINDOWS) add_definitions(-Werror=delete-incomplete) endif() 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.0.0-pre-alpha") set(KRITA_STABLE_VERSION_MAJOR 4) # 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MINOR 0) # 0 for 3.0, 1 for 3.1, etc. set(KRITA_VERSION_RELEASE 0) # 88 for pre-alpha, 89 for Alpha, increase for next test releases, set 0 for first Stable, etc. 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 2017) # 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_revision(GIT_REFSPEC GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1 AND GIT_BRANCH) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) endif() if(NOT DEFINED RELEASE_BUILD) # estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel") list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX) if (INDEX EQUAL -1) set(RELEASE_BUILD FALSE) else() set(RELEASE_BUILD TRUE) endif() endif() message(STATUS "Release build: ${RELEASE_BUILD}") # 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("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(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).") 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) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) find_package(PythonLibrary 3.6) 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) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.19 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 Archive Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) 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 ) 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") if (NOT WIN32 AND NOT APPLE) 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(KF5KIO ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5KIO_FOUND HAVE_KIO) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "KDE's KIO Framework" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used for recent document handling") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) find_package(XCB COMPONENTS XCB ATOM) set(HAVE_XCB ${XCB_FOUND}) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) set(HAVE_XCB FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -DQT_NO_URL_CAST_FROM_STRING ) if (${Qt5_VERSION} VERSION_GREATER "5.8.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800) elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700) else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600) endif() add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS_KRITADEVS "-O3 -g" CACHE STRING "" FORCE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") 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) + # enable exceptions globally kde_enable_exceptions() # only with this definition will all the FOO_TEST_EXPORT macro do something # TODO: check if this can be moved to only those places which make use of it, # to reduce global compiler definitions that would trigger a recompile of # everything on a change (like adding/removing tests to/from the build) if(BUILD_TESTING) add_definitions(-DCOMPILING_TESTS) endif() 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 set(KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) ########################### ############################ ## Required dependencies ## ############################ ########################### find_package(PNG REQUIRED) 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) # for pigment and stage -include_directories(${Boost_INCLUDE_DIRS}) +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 ) ########################### ############################ ## Optional dependencies ## ############################ ########################### ## ## Check for OpenEXR ## 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") 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") 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) 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) ## ## 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(Exiv2 0.16 REQUIRED) set_package_properties(Exiv2 PROPERTIES DESCRIPTION "Image metadata library and tools" URL "http://www.exiv2.org" PURPOSE "Required by Krita") ## ## 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() ## ## 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 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() 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 "-Wabi -ffp-contract=fast -fPIC") + set(ADDITIONAL_VC_FLAGS "-Wabi -ffp-contract=fast") + if(NOT WIN32) + set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") + endif() elseif (NOT MSVC) - set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast -fPIC") + set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast") + if(NOT WIN32) + set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") + endif() endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) if(WIN32) set(LIB_INSTALL_DIR ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} LIBRARY ${INSTALL_TARGETS_DEFAULT_ARGS} ARCHIVE ${INSTALL_TARGETS_DEFAULT_ARGS} ) endif() ## ## Test endianess ## 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.") ############################ ############################# ## 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) add_subdirectory(benchmarks) 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) message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 4031146311..01b8414ed0 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,94 +1,93 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ${CMAKE_SOURCE_DIR}/libs/pigment ${CMAKE_SOURCE_DIR}/libs/pigment/compositeops ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} - ${Boost_INCLUDE_DIRS} ) set(LINK_VC_LIB) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Vc_DEFINITIONS}") set(LINK_VC_LIB ${Vc_LIBRARIES}) endif() macro_add_unittest_definitions() ########### next target ############### set(kis_datamanager_benchmark_SRCS kis_datamanager_benchmark.cpp) set(kis_hiterator_benchmark_SRCS kis_hline_iterator_benchmark.cpp) set(kis_viterator_benchmark_SRCS kis_vline_iterator_benchmark.cpp) set(kis_random_iterator_benchmark_SRCS kis_random_iterator_benchmark.cpp) set(kis_projection_benchmark_SRCS kis_projection_benchmark.cpp) set(kis_bcontrast_benchmark_SRCS kis_bcontrast_benchmark.cpp) set(kis_blur_benchmark_SRCS kis_blur_benchmark.cpp) set(kis_level_filter_benchmark_SRCS kis_level_filter_benchmark.cpp) set(kis_painter_benchmark_SRCS kis_painter_benchmark.cpp) set(kis_stroke_benchmark_SRCS kis_stroke_benchmark.cpp) set(kis_fast_math_benchmark_SRCS kis_fast_math_benchmark.cpp) set(kis_floodfill_benchmark_SRCS kis_floodfill_benchmark.cpp) set(kis_gradient_benchmark_SRCS kis_gradient_benchmark.cpp) set(kis_mask_generator_benchmark_SRCS kis_mask_generator_benchmark.cpp) set(kis_low_memory_benchmark_SRCS kis_low_memory_benchmark.cpp) set(KisAnimationRenderingBenchmark_SRCS KisAnimationRenderingBenchmark.cpp) set(kis_filter_selections_benchmark_SRCS kis_filter_selections_benchmark.cpp) if (UNIX) # set(kis_composition_benchmark_SRCS kis_composition_benchmark.cpp) endif() set(kis_thumbnail_benchmark_SRCS kis_thumbnail_benchmark.cpp) krita_add_benchmark(KisDatamanagerBenchmark TESTNAME krita-benchmarks-KisDataManager ${kis_datamanager_benchmark_SRCS}) krita_add_benchmark(KisHLineIteratorBenchmark TESTNAME krita-benchmarks-KisHLineIterator ${kis_hiterator_benchmark_SRCS}) krita_add_benchmark(KisVLineIteratorBenchmark TESTNAME krita-benchmarks-KisVLineIterator ${kis_viterator_benchmark_SRCS}) krita_add_benchmark(KisRandomIteratorBenchmark TESTNAME krita-benchmarks-KisRandomIterator ${kis_random_iterator_benchmark_SRCS}) krita_add_benchmark(KisProjectionBenchmark TESTNAME krita-benchmarks-KisProjectionBenchmark ${kis_projection_benchmark_SRCS}) krita_add_benchmark(KisBContrastBenchmark TESTNAME krita-benchmarks-KisBContrastBenchmark ${kis_bcontrast_benchmark_SRCS}) krita_add_benchmark(KisBlurBenchmark TESTNAME krita-benchmarks-KisBlurBenchmark ${kis_blur_benchmark_SRCS}) krita_add_benchmark(KisLevelFilterBenchmark TESTNAME krita-benchmarks-KisLevelFilterBenchmark ${kis_level_filter_benchmark_SRCS}) krita_add_benchmark(KisPainterBenchmark TESTNAME krita-benchmarks-KisPainterBenchmark ${kis_painter_benchmark_SRCS}) krita_add_benchmark(KisStrokeBenchmark TESTNAME krita-benchmarks-KisStrokeBenchmark ${kis_stroke_benchmark_SRCS}) krita_add_benchmark(KisFastMathBenchmark TESTNAME krita-benchmarks-KisFastMath ${kis_fast_math_benchmark_SRCS}) krita_add_benchmark(KisFloodfillBenchmark TESTNAME krita-benchmarks-KisFloodFill ${kis_floodfill_benchmark_SRCS}) krita_add_benchmark(KisGradientBenchmark TESTNAME krita-benchmarks-KisGradientFill ${kis_gradient_benchmark_SRCS}) krita_add_benchmark(KisMaskGeneratorBenchmark TESTNAME krita-benchmarks-KisMaskGenerator ${kis_mask_generator_benchmark_SRCS}) krita_add_benchmark(KisLowMemoryBenchmark TESTNAME krita-benchmarks-KisLowMemory ${kis_low_memory_benchmark_SRCS}) krita_add_benchmark(KisAnimationRenderingBenchmark TESTNAME krita-benchmarks-KisAnimationRenderingBenchmark ${KisAnimationRenderingBenchmark_SRCS}) krita_add_benchmark(KisFilterSelectionsBenchmark TESTNAME krita-image-KisFilterSelectionsBenchmark ${kis_filter_selections_benchmark_SRCS}) if(UNIX) # krita_add_benchmark(KisCompositionBenchmark TESTNAME krita-benchmarks-KisComposition ${kis_composition_benchmark_SRCS}) endif() krita_add_benchmark(KisThumbnailBenchmark TESTNAME krita-benchmarks-KisThumbnail ${kis_thumbnail_benchmark_SRCS}) target_link_libraries(KisDatamanagerBenchmark kritaimage Qt5::Test) target_link_libraries(KisHLineIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisVLineIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisRandomIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisProjectionBenchmark kritaimage kritaui Qt5::Test) target_link_libraries(KisBContrastBenchmark kritaimage Qt5::Test) target_link_libraries(KisBlurBenchmark kritaimage Qt5::Test) target_link_libraries(KisLevelFilterBenchmark kritaimage Qt5::Test) target_link_libraries(KisPainterBenchmark kritaimage Qt5::Test) target_link_libraries(KisStrokeBenchmark kritaimage Qt5::Test) target_link_libraries(KisFastMathBenchmark kritaimage Qt5::Test) target_link_libraries(KisFloodfillBenchmark kritaimage Qt5::Test) target_link_libraries(KisGradientBenchmark kritaimage Qt5::Test) target_link_libraries(KisLowMemoryBenchmark kritaimage Qt5::Test) target_link_libraries(KisAnimationRenderingBenchmark kritaimage kritaui Qt5::Test) target_link_libraries(KisFilterSelectionsBenchmark kritaimage Qt5::Test) if(UNIX) # target_link_libraries(KisCompositionBenchmark kritaimage Qt5::Test ${LINK_VC_LIB}) if(HAVE_VC) # set_property(TARGET KisCompositionBenchmark APPEND PROPERTY COMPILE_OPTIONS "${Vc_ARCHITECTURE_FLAGS}") endif() endif() target_link_libraries(KisMaskGeneratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisThumbnailBenchmark kritaimage Qt5::Test) diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd index 37920aaa46..0e636a61aa 100644 --- a/build-tools/windows/build.cmd +++ b/build-tools/windows/build.cmd @@ -1,768 +1,767 @@ @echo off setlocal enabledelayedexpansion goto begin :: Subroutines :find_on_path out_variable file_name set %1=%~f$PATH:2 goto :EOF :get_dir_path out_variable file_path set %1=%~dp2 goto :EOF :get_full_path out_variable file_path setlocal set FULL_PATH=%~f2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) else ( if exist "%FULL_PATH%\" ( set FULL_PATH= ) ) endlocal & set "%1=%FULL_PATH%" goto :EOF :get_full_path_dir out_variable file_path setlocal set FULL_PATH=%~dp2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) endlocal & set "%1=%FULL_PATH%" goto :EOF :prompt_for_string out_variable prompt set /p %1=%~2^> goto :EOF :prompt_for_positive_integer out_variable prompt setlocal call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" set USER_INPUT=0 set /a RESULT=%USER_INPUT% if not %RESULT% GTR 0 ( set RESULT= ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_file out_variable prompt setlocal :prompt_for_file__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path RESULT "%USER_INPUT%" if "%RESULT%" == "" ( echo Input does not point to valid file! set USER_INPUT= goto prompt_for_file__retry ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_dir out_variable prompt setlocal :prompt_for_dir__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path_dir RESULT "%USER_INPUT%\" if "%RESULT%" == "" ( echo Input does not point to valid dir! set USER_INPUT= goto prompt_for_dir__retry ) endlocal & set "%1=%RESULT%" goto :EOF :usage echo Usage: echo %~n0 [--no-interactive] [ OPTIONS ... ] echo. echo Basic options: echo --no-interactive Run without interactive prompts echo When not specified, the script will prompt echo for some of the parameters. echo --jobs ^ Set parallel jobs count when building echo Defaults to no. of logical cores echo --skip-deps Skips (re)building of deps echo --skip-krita Skips (re)building of Krita echo. echo Path options: echo --src-dir ^ Specify Krita source dir echo If unspecified, this will be determined from echo the script location. echo --download-dir ^ Specify deps download dir echo Can be omitted if --skip-deps is used echo --deps-build-dir ^ Specify deps build dir echo Can be omitted if --skip-deps is used echo --deps-install-dir ^ Specify deps install dir echo --krita-build-dir ^ Specify Krita build dir echo Can be omitted if --skip-krita is used echo --krita-install-dir ^ Specify Krita install dir echo Can be omitted if --skip-krita is used echo. goto :EOF :usage_and_exit call :usage exit /b :usage_and_fail call :usage exit /b 100 :: ---------------------------- :begin echo Krita build script for Windows echo. :: command-line args parsing set ARG_NO_INTERACTIVE= set ARG_JOBS= set ARG_SKIP_DEPS= set ARG_SKIP_KRITA= set ARG_SRC_DIR= set ARG_DOWNLOAD_DIR= set ARG_DEPS_BUILD_DIR= set ARG_DEPS_INSTALL_DIR= set ARG_KRITA_BUILD_DIR= set ARG_KRITA_INSTALL_DIR= :args_parsing_loop set CURRENT_MATCHED= if not "%1" == "" ( if "%1" == "--no-interactive" ( set ARG_NO_INTERACTIVE=1 set CURRENT_MATCHED=1 ) if "%1" == "--jobs" ( if not "%ARG_JOBS%" == "" ( echo ERROR: Arg --jobs specified more than once 1>&2 echo. goto usage_and_fail ) set /a "ARG_JOBS=%2" if not !ARG_JOBS! GTR 0 ( echo ERROR: Arg --jobs is not a positive integer 1>&2 echo. goto usage_and_fail ) shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--skip-deps" ( set ARG_SKIP_DEPS=1 set CURRENT_MATCHED=1 ) if "%1" == "--skip-krita" ( set ARG_SKIP_KRITA=1 set CURRENT_MATCHED=1 ) if "%1" == "--src-dir" ( if not "%ARG_SRC_DIR%" == "" ( echo ERROR: Arg --src-dir specified more than once 1>&2 echo. goto usage_and_fail ) if not exist "%~f2\" ( echo ERROR: Arg --src-dir does not point to a directory 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_SRC_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--download-dir" ( if not "%ARG_DOWNLOAD_DIR%" == "" ( echo ERROR: Arg --download-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --download-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-build-dir" ( if not "%ARG_DEPS_BUILD_DIR%" == "" ( echo ERROR: Arg --deps-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-install-dir" ( if not "%ARG_DEPS_INSTALL_DIR%" == "" ( echo ERROR: Arg --deps-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-build-dir" ( if not "%ARG_KRITA_BUILD_DIR%" == "" ( echo ERROR: Arg --krita-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-install-dir" ( if not "%ARG_KRITA_INSTALL_DIR%" == "" ( echo ERROR: Arg --krita-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--help" ( goto usage_and_exit ) if not "!CURRENT_MATCHED!" == "1" ( echo ERROR: Unknown option %1 1>&2 echo. goto usage_and_fail ) shift /1 goto args_parsing_loop ) if "%ARG_NO_INTERACTIVE%" == "1" ( echo Non-interactive mode ) else ( echo Interactive mode :: Trick to pause on exit call :real_begin pause exit /b !ERRORLEVEL! ) :real_begin echo. if "%ARG_SKIP_DEPS%" == "1" ( if "%ARG_SKIP_KRITA%" == "1" ( echo ERROR: You cannot skip both deps and Krita 1>&2 echo. exit /b 102 ) echo Building of deps will be skipped. ) else ( if "%ARG_SKIP_KRITA%" == "1" ( echo Building of Krita will be skipped. ) else ( echo Both deps and Krita will be built. ) ) :: Check environment config if "%CMAKE_EXE%" == "" ( call :find_on_path CMAKE_EXE cmake.exe if "!CMAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" ) if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) else ( echo Found CMake on PATH: !CMAKE_EXE! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) ) ) ) echo CMake: %CMAKE_EXE% if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7z.exe if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe" if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe" ) if "!SEVENZIP_EXE!" == "" ( echo 7-Zip not found ) ) ) if "%SEVENZIP_EXE%" == "" ( echo 7-Zip: %SEVENZIP_EXE% ) if "%MINGW_BIN_DIR%" == "" ( call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" ) if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) else ( call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" echo Found mingw on PATH: !MINGW_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) ) ) ) echo mingw-w64: %MINGW_BIN_DIR% if "%PYTHON_BIN_DIR%" == "" ( call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" ) if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) else ( call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" echo Found Python on PATH: !PYTHON_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) ) ) ) echo Python: %PYTHON_BIN_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if exist "%WindowsSdkDir%\" ( pushd "%WindowsSdkDir%" if exist "bin\x64\fxc.exe" ( set HAVE_FXC_EXE=1 ) else ( for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( if exist "bin\%%a\x64\fxc.exe" ( set HAVE_FXC_EXE=1 ) ) ) popd ) set QT_ENABLE_DYNAMIC_OPENGL=ON if not "%HAVE_FXC_EXE%" == "1" ( set WindowsSdkDir= echo Windows SDK 10 with fxc.exe not found echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 102 ) ) set QT_ENABLE_DYNAMIC_OPENGL=OFF ) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% :skip_windows_sdk_dir_check if not "%ARG_JOBS%" == "" ( set "PARALLEL_JOBS=%ARG_JOBS%" ) if "%PARALLEL_JOBS%" == "" ( echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS% echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS% if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs" if "!PARALLEL_JOBS!" == "" ( echo ERROR: Invalid job count! 1>&2 exit /b 102 ) ) ) ) echo Parallel jobs count: %PARALLEL_JOBS% if not "%ARG_SRC_DIR%" == "" ( set "KRITA_SRC_DIR=%ARG_SRC_DIR%" ) if "%KRITA_SRC_DIR%" == "" ( :: Check whether this looks like to be in the source tree set "_temp=%~dp0" if "!_temp:~-21!" == "\build-tools\windows\" ( if exist "!_temp:~0,-21!\CMakeLists.txt" ( if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" ( set "KRITA_SRC_DIR=!_temp:~0,-21!\" echo Script is running inside Krita src dir ) ) ) ) if "%KRITA_SRC_DIR%" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir" ) if "!KRITA_SRC_DIR!" == "" ( echo ERROR: Krita src dir not found! 1>&2 exit /b 102 ) ) echo Krita src: %KRITA_SRC_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check if not "%ARG_DOWNLOAD_DIR%" == "" ( set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%" ) if "%DEPS_DOWNLOAD_DIR%" == "" ( set DEPS_DOWNLOAD_DIR=%CD%\d\ echo Using default deps download dir: !DEPS_DOWNLOAD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir" ) ) if "!DEPS_DOWNLOAD_DIR!" == "" ( echo ERROR: Deps download dir not set! 1>&2 exit /b 102 ) ) echo Deps download dir: %DEPS_DOWNLOAD_DIR% if not "%ARG_DEPS_BUILD_DIR%" == "" ( set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%" ) if "%DEPS_BUILD_DIR%" == "" ( set DEPS_BUILD_DIR=%CD%\b_deps\ echo Using default deps build dir: !DEPS_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir" ) ) if "!DEPS_BUILD_DIR!" == "" ( echo ERROR: Deps build dir not set! 1>&2 exit /b 102 ) ) echo Deps build dir: %DEPS_BUILD_DIR% :skip_deps_args_check if not "%ARG_DEPS_INSTALL_DIR%" == "" ( set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%" ) if "%DEPS_INSTALL_DIR%" == "" ( set DEPS_INSTALL_DIR=%CD%\i_deps\ echo Using default deps install dir: !DEPS_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir" ) ) if "!DEPS_INSTALL_DIR!" == "" ( echo ERROR: Deps install dir not set! 1>&2 exit /b 102 ) ) echo Deps install dir: %DEPS_INSTALL_DIR% if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check if not "%ARG_KRITA_BUILD_DIR%" == "" ( set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%" ) if "%KRITA_BUILD_DIR%" == "" ( set KRITA_BUILD_DIR=%CD%\b\ echo Using default Krita build dir: !KRITA_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir" ) ) if "!KRITA_BUILD_DIR!" == "" ( echo ERROR: Krita build dir not set! 1>&2 exit /b 102 ) ) echo Krita build dir: %KRITA_BUILD_DIR% if not "%ARG_KRITA_INSTALL_DIR%" == "" ( set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%" ) if "%KRITA_INSTALL_DIR%" == "" ( set KRITA_INSTALL_DIR=%CD%\i\ echo Using default Krita install dir: !KRITA_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir" ) ) if "!KRITA_INSTALL_DIR!" == "" ( echo ERROR: Krita install dir not set! 1>&2 exit /b 102 ) ) echo Krita install dir: %KRITA_INSTALL_DIR% :skip_krita_args_check echo. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is the above ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 1 ) echo. ) :: Initialize clean PATH set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\ set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH% echo Creating dirs... if NOT "%ARG_SKIP_DEPS%" == "1" ( mkdir %DEPS_DOWNLOAD_DIR% if errorlevel 1 ( if not exist "%DEPS_DOWNLOAD_DIR%\" ( echo ERROR: Cannot create deps download dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_BUILD_DIR% if errorlevel 1 ( if not exist "%DEPS_BUILD_DIR%\" ( echo ERROR: Cannot create deps build dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_INSTALL_DIR% if errorlevel 1 ( if not exist "%DEPS_INSTALL_DIR%\" ( echo ERROR: Cannot create deps install dir! 1>&2 exit /b 103 ) ) ) if NOT "%ARG_SKIP_KRITA%" == "1" ( mkdir %KRITA_BUILD_DIR% if errorlevel 1 ( if not exist "%KRITA_BUILD_DIR%\" ( echo ERROR: Cannot create Krita build dir! 1>&2 exit /b 103 ) ) mkdir %KRITA_INSTALL_DIR% if errorlevel 1 ( if not exist "%KRITA_INSTALL_DIR%\" ( echo ERROR: Cannot create Krita install dir! 1>&2 exit /b 103 ) ) ) echo. set CMAKE_BUILD_TYPE=RelWithDebInfo set QT_ENABLE_DEBUG_INFO=OFF :: Paths for CMake set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%" set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %" set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%" set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %" set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%" set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %" set PATH=%DEPS_INSTALL_DIR%\bin;%PATH% if not "%GETTEXT_SEARCH_PATH%" == "" ( set PATH=%PATH%;%GETTEXT_SEARCH_PATH% ) if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps pushd %DEPS_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter deps build dir! 1>&2 exit /b 104 ) echo Running CMake for deps... "%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ -DSUBMAKE_JOBS=%PARALLEL_JOBS% ^ -DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^ -DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^ -DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^ -DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% if errorlevel 1 ( echo ERROR: CMake configure failed! 1>&2 exit /b 104 ) echo. set EXT_TARGETS=patch png2ico gettext qt zlib boost eigen3 exiv2 fftw3 ilmbase set EXT_TARGETS=%EXT_TARGETS% jpeg lcms2 ocio openexr png tiff gsl vc libraw set EXT_TARGETS=%EXT_TARGETS% freetype poppler kwindowsystem drmingw gmic set EXT_TARGETS=%EXT_TARGETS% python sip pyqt for %%a in (%EXT_TARGETS%) do ( echo Building ext_%%a... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a if errorlevel 1 ( echo ERROR: Building of ext_%%a failed! 1>&2 exit /b 105 ) - echo. ) echo. echo ******** Built deps ******** popd :skip_build_deps if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita pushd %KRITA_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter Krita build dir! 1>&2 exit /b 104 ) echo Running CMake for Krita... "%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ -DBoost_DEBUG=OFF ^ -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ -DBUILD_TESTING=OFF ^ -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ -Wno-dev ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% if errorlevel 1 ( echo ERROR: CMake configure failed! 1>&2 exit /b 104 ) echo. echo Building Krita... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS% if errorlevel 1 ( echo ERROR: Building of Krita failed! 1>&2 exit /b 105 ) echo. echo ******** Built Krita ******** popd :skip_build_krita echo Krita build completed! diff --git a/cmake/modules/GetGitRevisionDescription.cmake b/cmake/modules/GetGitRevisionDescription.cmake index 28db419cca..4550c6af4c 100644 --- a/cmake/modules/GetGitRevisionDescription.cmake +++ b/cmake/modules/GetGitRevisionDescription.cmake @@ -1,157 +1,161 @@ # - Returns a version string from Git # # These functions force a re-configure on each git commit so that you can # trust the values of the variables in your build system. # # get_git_head_revision( [ ...]) # # Returns the refspec and sha hash of the current head revision # # git_describe( [ ...]) # # Returns the results of git describe on the source tree, and adjusting # the output so that it tests false if an error occurs. # # git_get_exact_tag( [ ...]) # # Returns the results of git describe --exact-match on the source tree, # and adjusting the output so that it tests false if there was no exact # matching tag. # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) if(__get_git_revision_description) return() endif() set(__get_git_revision_description YES) # We must run the following at "include" time, not at function call time, # to find the path to this module rather than the path to a calling list file get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) function(get_git_head_revision _refspecvar _hashvar) set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(GIT_DIR "${GIT_PARENT_DIR}/.git") while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) # We have reached the root directory, we are not in git set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) return() endif() set(GIT_DIR "${GIT_PARENT_DIR}/.git") endwhile() - # check if this is a submodule + # check if this is a linked working tree (e.g. submodule or git-worktree) if(NOT IS_DIRECTORY ${GIT_DIR}) - file(READ ${GIT_DIR} submodule) - string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) - get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) - get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + file(READ ${GIT_DIR} gitdirfile) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_PATH ${gitdirfile}) + if(IS_ABSOLUTE ${GIT_DIR_PATH}) + get_filename_component(GIT_DIR ${GIT_DIR_PATH} ABSOLUTE) + else() + get_filename_component(LINKED_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${LINKED_DIR}/${GIT_DIR_PATH} ABSOLUTE) + endif() endif() set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") if(NOT EXISTS "${GIT_DATA}") file(MAKE_DIRECTORY "${GIT_DATA}") endif() if(NOT EXISTS "${GIT_DIR}/HEAD") return() endif() set(HEAD_FILE "${GIT_DATA}/HEAD") configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) endfunction() function(git_describe _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() get_git_head_revision(refspec hash) if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() if(NOT hash) set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) return() endif() # TODO sanitize #if((${ARGN}" MATCHES "&&") OR # (ARGN MATCHES "||") OR # (ARGN MATCHES "\\;")) # message("Please report the following error to the project!") # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") #endif() #message(STATUS "Arguments to execute_process: ${ARGN}") execute_process(COMMAND "${GIT_EXECUTABLE}" describe ${hash} ${ARGN} WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(get_git_branch _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() execute_process(COMMAND "${GIT_EXECUTABLE}" symbolic-ref --short HEAD WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_get_exact_tag _var) git_describe(out --exact-match ${ARGN}) set(${_var} "${out}" PARENT_SCOPE) endfunction() diff --git a/cmake/modules/GetGitRevisionDescription.cmake.in b/cmake/modules/GetGitRevisionDescription.cmake.in index 888ce13aab..afd305cd5e 100644 --- a/cmake/modules/GetGitRevisionDescription.cmake.in +++ b/cmake/modules/GetGitRevisionDescription.cmake.in @@ -1,38 +1,50 @@ # # Internal file for GetGitRevisionDescription.cmake # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +set(GIT_DIR "@GIT_DIR@") +# handle git-worktree +if(EXISTS "${GIT_DIR}/commondir") + file(READ "${GIT_DIR}/commondir" GIT_DIR_NEW LIMIT 1024) + string(STRIP "${GIT_DIR_NEW}" GIT_DIR_NEW) + if(NOT IS_ABSOLUTE "${GIT_DIR_NEW}") + get_filename_component(GIT_DIR_NEW ${GIT_DIR}/${GIT_DIR_NEW} ABSOLUTE) + endif() + if(EXISTS "${GIT_DIR_NEW}") + set(GIT_DIR "${GIT_DIR_NEW}") + endif() +endif() if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") - if(EXISTS "@GIT_DIR@/${HEAD_REF}") - configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") - configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + if(EXISTS "${GIT_DIR}/${HEAD_REF}") + configure_file("${GIT_DIR}/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + elseif(EXISTS "${GIT_DIR}/logs/${HEAD_REF}") + configure_file("${GIT_DIR}/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) set(HEAD_HASH "${HEAD_REF}") endif() else() # detached HEAD - configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) + configure_file("${GIT_DIR}/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() diff --git a/krita/data/profiles/elles-icc-profiles/CMakeLists.txt b/krita/data/profiles/elles-icc-profiles/CMakeLists.txt index 1b3d466513..725f634049 100644 --- a/krita/data/profiles/elles-icc-profiles/CMakeLists.txt +++ b/krita/data/profiles/elles-icc-profiles/CMakeLists.txt @@ -1,28 +1,25 @@ ########### install files ############### install(FILES ACEScg-elle-V4-g10.icc ClayRGB-elle-V4-srgbtrc.icc Gray-D50-elle-V2-g10.icc Gray-D50-elle-V2-g18.icc Gray-D50-elle-V2-g22.icc Gray-D50-elle-V2-srgbtrc.icc Gray-D50-elle-V4-g10.icc Gray-D50-elle-V4-g18.icc Gray-D50-elle-V4-g22.icc Gray-D50-elle-V4-srgbtrc.icc Gray-D50-elle-V2-labl.icc Gray-D50-elle-V4-labl.icc Gray-D50-elle-V2-rec709.icc Gray-D50-elle-V4-rec709.icc - Lab-D50-Identity-elle-V2.icc - Lab-D50-Identity-elle-V4.icc - sRGB-elle-V2-g10.icc sRGB-elle-V2-srgbtrc.icc Lab-D50-Identity-elle-V2.icc Lab-D50-Identity-elle-V4.icc XYZ-D50-Identity-elle-V4.icc DESTINATION ${SHARE_INSTALL_PREFIX}/color/icc/krita) diff --git a/krita/krita.action b/krita/krita.action index cbc3dfbe8c..6015284b87 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3090 +1,3102 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Show system information for bug reports. Show system information for bug reports. Show system information for bug reports. false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false + + + Use multiple of 2 for pixel scale + Use multiple of 2 for pixel scale + Use multiple of 2 for pixel scale + Use multiple of 2 for pixel scale + 1 + 0 + + true + + Painting lightness-increase Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false lightness-decrease Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false opacity-increase Increase opacity Increase opacity Increase opacity 0 0 O false opacity-decrease Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 10000 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 10000 true Hide Mirror X Line Hide Mirror X Line Hide Mirror X Line 10000 true Hide Mirror Y Line Hide Mirror Y Line Hide Mirror Y Line 10000 true Lock Lock X Line Lock X Line 10000 true Lock Y Line Lock Y Line Lock Y Line 10000 true Move to Canvas Center Move to Canvas Center X Move to Canvas Center X 10000 false Move to Canvas Center Y Move to Canvas Center Y Move to Canvas Center Y 10000 false &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false brushsize-decrease Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false brushsize-increase Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Select Opaque Select Opaque Select Opaque 100000 100 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance.. Color Balance.. Color Balance.. 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false Crop Tool Crop the image to an area Crop the image to an area C false Polygon Tool Polygon Tool. Shift-mouseclick ends the polygon. Polygon Tool. Shift-mouseclick ends the polygon. false References References References false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Lazy Brush Tool Lazy Brush Tool Lazy Brush Tool Smart Patch Tool Smart Patch Tool Smart Patch Tool Pan Tool Pan Tool Pan Tool Shape Manipulation Tool Shape Manipulation Tool Shape Manipulation Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Text Editing Tool Text editing Text editing false Outline Selection Tool Outline Selection Tool Outline Selection Tool false Artistic Text Tool Artistic text editing Artistic text editing false Bezier Curve Selection Tool Select a Bezier Curve Selection Tool false Similar Color Selection Tool Select a Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool Bezier Curve Tool. Shift-mouseclick ends the curve. Bezier Curve Tool. Shift-mouseclick ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Zoom Tool Zoom Tool Zoom Tool false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false - + - Ruler assistant editor tool + Assistant Tool - Ruler assistant editor tool - Ruler assistant editor tool + Assistant Tool + Assistant Tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false Add blank frame Add blank frame Add blank frame 100000 0 false Copy Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Add blank frame Add blank frame Add blank frame 100000 0 false Show in Timeline true Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 1000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 1000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 1000 0 false Quick Group Create a group layer containing selected layers Quick Group 100000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false fileLayer to &File Layer Saves out the layers into a new image and then references that image. Convert to File Layer 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_ruler_assistant.svg b/krita/pics/tools/SVG/16/dark_krita_tool_assistant.svg similarity index 100% rename from krita/pics/tools/SVG/16/dark_krita_tool_ruler_assistant.svg rename to krita/pics/tools/SVG/16/dark_krita_tool_assistant.svg diff --git a/krita/pics/tools/SVG/16/light_krita_tool_ruler_assistant.svg b/krita/pics/tools/SVG/16/light_krita_tool_assistant.svg similarity index 100% rename from krita/pics/tools/SVG/16/light_krita_tool_ruler_assistant.svg rename to krita/pics/tools/SVG/16/light_krita_tool_assistant.svg diff --git a/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc b/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc index f440a8eeb1..ffa77b7afd 100644 --- a/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc +++ b/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc @@ -1,81 +1,80 @@ - - - + + dark_calligraphy.svg dark_draw-text.svg dark_format-fill-color.svg dark_krita_draw_path.svg dark_krita_tool_color_fill.svg dark_krita_tool_color_picker.svg dark_krita_tool_dyna.svg dark_krita_tool_ellipse.svg dark_krita_tool_freehand.svg dark_krita_tool_freehandvector.svg dark_krita_tool_gradient.svg dark_krita_tool_grid.svg dark_krita_tool_line.svg dark_krita_tool_measure.svg dark_krita_tool_move.svg dark_krita_tool_multihand.svg dark_krita_tool_polygon.svg dark_krita_tool_rectangle.svg - dark_krita_tool_ruler_assistant.svg dark_krita_tool_transform.svg dark_pattern.svg dark_polyline.svg dark_select.svg dark_tool_contiguous_selection.svg dark_tool_crop.svg dark_tool_elliptical_selection.svg dark_tool_outline_selection.svg dark_tool_pan.svg dark_tool_path_selection.svg dark_tool_perspectivegrid.svg dark_tool_polygonal_selection.svg dark_tool_rect_selection.svg dark_tool_similar_selection.svg dark_tool_zoom.svg light_calligraphy.svg light_draw-text.svg light_format-fill-color.svg light_krita_draw_path.svg light_krita_tool_color_fill.svg light_krita_tool_color_picker.svg light_krita_tool_dyna.svg light_krita_tool_ellipse.svg light_krita_tool_freehand.svg light_krita_tool_freehandvector.svg light_krita_tool_gradient.svg light_krita_tool_grid.svg light_krita_tool_line.svg light_krita_tool_measure.svg light_krita_tool_move.svg light_krita_tool_multihand.svg light_krita_tool_polygon.svg light_krita_tool_rectangle.svg - light_krita_tool_ruler_assistant.svg light_krita_tool_transform.svg light_pattern.svg light_polyline.svg light_select.svg light_tool_contiguous_selection.svg light_tool_crop.svg light_tool_elliptical_selection.svg light_tool_outline_selection.svg light_tool_pan.svg light_tool_path_selection.svg light_tool_perspectivegrid.svg light_tool_polygonal_selection.svg light_tool_rect_selection.svg light_tool_similar_selection.svg light_tool_zoom.svg dark_shape_handling.svg dark_artistic_text.svg light_artistic_text.svg light_shape_handling.svg dark_krita_tool_lazybrush.svg light_krita_tool_lazybrush.svg dark_krita_tool_smart_patch.svg light_krita_tool_smart_patch.svg + light_krita_tool_assistant.svg + dark_krita_tool_assistant.svg diff --git a/libs/flake/KoShape.h b/libs/flake/KoShape.h index 2005eabadd..e54723a70d 100644 --- a/libs/flake/KoShape.h +++ b/libs/flake/KoShape.h @@ -1,1290 +1,1290 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006, 2008 C. Boemann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2007-2009,2011 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPE_H #define KOSHAPE_H #include "KoFlake.h" #include "KoFlakeTypes.h" #include "KoConnectionPoint.h" #include #include #include #include #include "kritaflake_export.h" class QPainter; class QRectF; class QPainterPath; class QTransform; class KoShapeContainer; class KoShapeStrokeModel; class KoShapeUserData; class KoViewConverter; class KoShapeApplicationData; class KoShapeSavingContext; class KoShapeLoadingContext; class KoGenStyle; class KoShapeShadow; class KoShapePrivate; class KoFilterEffectStack; class KoSnapData; class KoClipPath; class KoClipMask; class KoShapePaintingContext; class KoShapeAnchor; class KoBorder; struct KoInsets; class KoShapeBackground; class KisHandlePainterHelper; /** * * Base class for all flake shapes. Shapes extend this class * to allow themselves to be manipulated. This class just represents * a graphical shape in the document and can be manipulated by some default * tools in this library. * * Due to the limited responsibility of this class, the extending object * can have any data backend and is responsible for painting itself. * * We strongly suggest that any extending class will use a Model View * Controller (MVC) design where the View part is all in this class, as well * as the one that inherits from this one. This allows the data that rests * in the model to be reused in different parts of the document. For example * by having two flake objects that show that same data. Or each showing a section of it. * * The KoShape data is completely in postscript-points (pt) (see KoUnit * for conversion methods to and from points). * This image will explain the real-world use of the shape and its options. *
* The Rotation center can be returned with absolutePosition() * *

Flake objects can be created in three ways: *

    *
  • a simple new KoDerivedFlake(), *
  • through an associated tool, *
  • through a factory *
* *

Shape interaction notifications

* We had several notification methods that allow your shape to be notified of changes in other * shapes positions or rotation etc. *
  1. The most general is KoShape::shapeChanged().
    * a virtual method that you can use to check various changed to your shape made by tools or otherwise.
  2. *
  3. for shape hierarchies the parent may receive a notification when a child was modified. * This is done though KoShapeContainerModel::childChanged()
  4. *
  5. any shape that is at a similar position as another shape there is collision detection. * You can register your shape to be sensitive to any changes like moving or whatever to * other shapes that intersect yours. * Such changes will then be notified to your shape using the method from (1) You should call * KoShape::setCollisionDetection(bool) to enable this. *
*/ class KRITAFLAKE_EXPORT KoShape { public: /// Used by shapeChanged() to select which change was made enum ChangeType { PositionChanged, ///< used after a setPosition() RotationChanged, ///< used after a setRotation() ScaleChanged, ///< used after a scale() ShearChanged, ///< used after a shear() SizeChanged, ///< used after a setSize() GenericMatrixChange, ///< used after the matrix was changed without knowing which property explicitly changed KeepAspectRatioChange, ///< used after setKeepAspectRatio() ParentChanged, ///< used after a setParent() CollisionDetected, ///< used when another shape moved in our boundingrect Deleted, ///< the shape was deleted StrokeChanged, ///< the shapes stroke has changed BackgroundChanged, ///< the shapes background has changed ShadowChanged, ///< the shapes shadow has changed BorderChanged, ///< the shapes border has changed ParameterChanged, ///< the shapes parameter has changed (KoParameterShape only) ContentChanged, ///< the content of the shape changed e.g. a new image inside a pixmap/text change inside a textshape TextRunAroundChanged, ///< used after a setTextRunAroundSide() ChildChanged, ///< a child of a container was changed/removed. This is propagated to all parents ConnectionPointChanged, ///< a connection point has changed ClipPathChanged, ///< the shapes clip path has changed TransparencyChanged ///< the shapetransparency value has changed }; /// The behavior text should do when intersecting this shape. enum TextRunAroundSide { BiggestRunAroundSide, ///< Run other text around the side that has the most space LeftRunAroundSide, ///< Run other text around the left side of the frame RightRunAroundSide, ///< Run other text around the right side of the frame EnoughRunAroundSide, ///< Run other text dynamically around both sides of the shape, provided there is sufficient space left BothRunAroundSide, ///< Run other text around both sides of the shape NoRunAround, ///< The text will be completely avoiding the frame by keeping the horizontal space that this frame occupies blank. RunThrough ///< The text will completely ignore the frame and layout as if it was not there }; /// The behavior text should do when intersecting this shape. enum TextRunAroundContour { ContourBox, /// Run other text around a bounding rect of the outline ContourFull, ///< Run other text around also on the inside ContourOutside ///< Run other text around only on the outside }; /** * TODO */ enum RunThroughLevel { Background, Foreground }; /** * @brief Constructor */ KoShape(); /** * @brief Destructor */ virtual ~KoShape(); /** * @brief creates a deep copy of thie shape or shapes subtree * @return a cloned shape */ virtual KoShape* cloneShape() const; /** * @brief Paint the shape fill * The class extending this one is responsible for painting itself. Since we do not * assume the shape is square the paint must also clear its background if it will draw * something transparent on top. * This can be done with a method like: * painter.fillRect(converter.normalToView(QRectF(QPointF(0.0,0.0), size())), background()); * Or equavalent for non-square objects. * Do note that a shape's top-left is always at coordinate 0,0. Even if the shape itself is rotated * or translated. * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @see applyConversion() * @param paintcontext the painting context. */ virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) = 0; /** * @brief paintStroke paints the shape's stroked outline * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @see applyConversion() * @param paintcontext the painting context. */ virtual void paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext); /** * @brief Paint the shape's border * This is a helper function that could be called from the paint() method of all shapes. * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @see applyConversion() */ virtual void paintBorder(QPainter &painter, const KoViewConverter &converter); /** * Load a shape from odf * * @param context the KoShapeLoadingContext used for loading * @param element element which represents the shape in odf * * @return false if loading failed */ virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; /** * @brief store the shape data as ODF XML. * This is the method that will be called when saving a shape as a described in * OpenDocument 9.2 Drawing Shapes. * @see saveOdfAttributes */ virtual void saveOdf(KoShapeSavingContext &context) const = 0; /** * This method can be used while saving the shape as ODF to add the data * stored on this shape to the current element. * * @param context the context for the current save. * @param attributes a number of OdfAttribute items to state which attributes to save. * @see saveOdf */ void saveOdfAttributes(KoShapeSavingContext &context, int attributes) const; /** * This method can be used while saving the shape as Odf to add common child elements * * The office:event-listeners and draw:glue-point are saved. * @param context the context for the current save. */ void saveOdfCommonChildElements(KoShapeSavingContext &context) const; /** * This method can be used to save contour data from the clipPath() * * The draw:contour-polygon or draw:contour-path elements are saved. * @param context the context for the current save. * @param originalSize the original size of the unscaled image. */ void saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const; /** * @brief Scale the shape using the zero-point which is the top-left corner. * @see position() * * @param sx scale in x direction * @param sy scale in y direction */ void scale(qreal sx, qreal sy); /** * @brief Rotate the shape (relative) * * The shape will be rotated from the current rotation using the center of the shape using the size() * * @param angle change the angle of rotation increasing it with 'angle' degrees */ void rotate(qreal angle); /** * Return the current rotation in degrees. * It returns NaN if the shape has a shearing or scaling transformation applied. */ qreal rotation() const; /** * @brief Shear the shape * The shape will be sheared using the zero-point which is the top-left corner. * @see position() * * @param sx shear in x direction * @param sy shear in y direction */ void shear(qreal sx, qreal sy); /** * @brief Resize the shape * * @param size the new size of the shape. This is different from scaling as * scaling is a so called secondary operation which is comparable to zooming in * instead of changing the size of the basic shape. * Easiest example of this difference is that using this method will not distort the * size of pattern-fills and strokes. */ virtual void setSize(const QSizeF &size); /** * @brief Get the size of the shape in pt. * * The size is in shape coordinates. * * @return the size of the shape as set by setSize() */ virtual QSizeF size() const; /** * @brief Set the position of the shape in pt * * @param position the new position of the shape */ virtual void setPosition(const QPointF &position); /** * @brief Get the position of the shape in pt * * @return the position of the shape */ QPointF position() const; /** * @brief Check if the shape is hit on position * @param position the position where the user clicked. * @return true when it hits. */ virtual bool hitTest(const QPointF &position) const; /** * @brief Get the bounding box of the shape * * This includes the line width and the shadow of the shape * * @return the bounding box of the shape */ virtual QRectF boundingRect() const; /** * Get the united bounding box of a group of shapes. This is a utility * function used in many places in Krita. */ static QRectF boundingRect(const QList &shapes); /** * @return the bounding rect of the outline of the shape measured * in absolute coordinate system. Please note that in contrast to * boundingRect() this rect doesn't include the stroke and other * insets. */ QRectF absoluteOutlineRect(KoViewConverter *converter = 0) const; /** * Same as a member function, but applies to a list of shapes and returns a * united rect. */ static QRectF absoluteOutlineRect(const QList &shapes, KoViewConverter *converter = 0); /** * @brief Add a connector point to the shape * * A connector is a place on the shape that allows a graphical connection to be made * using a line, for example. * * @param point the connection point to add * @return the id of the new connection point */ int addConnectionPoint(const KoConnectionPoint &point); /** * Sets data of connection point with specified id. * * The position of the connector is restricted to the bounding rectangle of the shape. * When setting a default connection point, the new position is ignored, as these * are fixed at their default position. * The function will insert a new connection point if the specified id was not used * before. * * @param connectionPointId the id of the connection point to set * @param point the connection point data * @return false if specified connection point id is invalid, else true */ bool setConnectionPoint(int connectionPointId, const KoConnectionPoint &point); /// Checks if a connection point with the specified id exists bool hasConnectionPoint(int connectionPointId) const; /// Returns connection point with specified connection point id KoConnectionPoint connectionPoint(int connectionPointId) const; /** * Return a list of the connection points that have been added to this shape. * All the points are relative to the shape position, see absolutePosition(). * @return a list of the connectors that have been added to this shape. */ KoConnectionPoints connectionPoints() const; /// Removes connection point with specified id void removeConnectionPoint(int connectionPointId); /// Removes all connection points void clearConnectionPoints(); /** * Return the side text should flow around this shape. This implements the ODF style:wrap * attribute that specifies how text is displayed around a frame or graphic object. */ TextRunAroundSide textRunAroundSide() const; /** * Set the side text should flow around this shape. * @param side the requested side * @param runThrought run through the foreground or background or... */ void setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrough = Background); /** * The space between this shape's left edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceLeft() const; /** * Set the space between this shape's left edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceLeft(qreal distance); /** * The space between this shape's top edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceTop() const; /** * Set the space between this shape's top edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceTop(qreal distance); /** * The space between this shape's right edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceRight() const; /** * Set the space between this shape's right edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceRight(qreal distance); /** * The space between this shape's bottom edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceBottom() const; /** * Set the space between this shape's bottom edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceBottom(qreal distance); /** * Return the threshold above which text should flow around this shape. * The text will not flow around the shape on a side unless the space available on that side * is above this threshold. Only used when the text run around side is EnoughRunAroundSide. * @return threshold the threshold */ qreal textRunAroundThreshold() const; /** * Set the threshold above which text should flow around this shape. * The text will not flow around the shape on a side unless the space available on that side * is above this threshold. Only used when the text run around side is EnoughRunAroundSide. * @param threshold the new threshold */ void setTextRunAroundThreshold(qreal threshold); /** * Return the how tight text run around is done around this shape. * @return the contour */ TextRunAroundContour textRunAroundContour() const; /** * Set how tight text run around is done around this shape. * @param contour the new contour */ void setTextRunAroundContour(TextRunAroundContour contour); /** * Set the KoShapeAnchor */ void setAnchor(KoShapeAnchor *anchor); /** * Return the KoShapeAnchor, or 0 */ KoShapeAnchor *anchor() const; /** * Set the minimum height of the shape. * Currently it's not respected but only for informational purpose * @param minimumShapeHeight the minimum height of the frame. */ void setMinimumHeight(qreal height); /** * Return the minimum height of the shape. * @return the minimum height of the shape. Default is 0.0. */ qreal minimumHeight() const; /** * Set the background of the shape. * A shape background can be a plain color, a gradient, a pattern, be fully transparent * or have a complex fill. * Setting such a background will allow the shape to be filled and will be able to tell * if it is transparent or not. * * If the shape inherited the background from its parent, its stops inheriting it, that * is inheritBackground property resets to false. * * @param background the new shape background. */ void setBackground(QSharedPointer background); /** * return the brush used to paint te background of this shape with. * A QBrush can have a plain color, be fully transparent or have a complex fill. * setting such a brush will allow the shape to fill itself using that brush and * will be able to tell if its transparent or not. * @return the background-brush */ QSharedPointer background() const; /** * @brief setInheritBackground marks a shape as inhiriting the background * from the parent shape. NOTE: The currently selected background is destroyed. * @param value true if the shape should inherit the filling background */ void setInheritBackground(bool value); /** * @brief inheritBackground shows if the shape inherits background from its parent * @return true if the shape inherits the fill */ bool inheritBackground() const; /** * Returns true if there is some transparency, false if the shape is fully opaque. * The default implementation will just return if the background has some transparency, * you should override it and always return true if your shape is not square. * @return if the shape is (partly) transparent. */ virtual bool hasTransparency() const; /** * Sets shape level transparency. * @param transparency the new shape level transparency */ void setTransparency(qreal transparency); /** * Returns the shape level transparency. * @param recursive when true takes the parents transparency into account */ qreal transparency(bool recursive=false) const; /** * Retrieve the z-coordinate of this shape. * The zIndex property is used to determine which shape lies on top of other objects. * An shape with a higher z-order is on top, and can obscure another shape. * @return the z-index of this shape. * @see setZIndex() */ int zIndex() const; /** * Set the z-coordinate of this shape. * The zIndex property is used to determine which shape lies on top of other objects. * An shape with a higher z-order is on top, and can obscure, another shape. *

Just like two objects having the same x or y coordinate will make them 'touch', * so will two objects with the same z-index touch on the z plane. In layering the * shape this, however, can cause a little confusion as one always has to be on top. * The layering if two overlapping objects have the same index is implementation dependent * and probably depends on the order in which they are added to the shape manager. * @param zIndex the new z-index; */ void setZIndex(int zIndex); /** * Retrieve the run through property of this shape. * The run through property is used to determine if the shape is behind, inside or before text. * @return the run through of this shape. */ int runThrough(); /** * Set the run through property of this shape. * The run through property is used to determine if the shape is behind, inside or before text. * @param runThrough the new run through; */ virtual void setRunThrough(short int runThrough); /** * Changes the Shape to be visible or invisible. * Being visible means being painted, as well as being used for * things like guidelines or searches. * @param on when true; set the shape to be visible. * @see setGeometryProtected(), setContentProtected(), setSelectable() */ void setVisible(bool on); /** * Returns current visibility state of this shape. * Being visible means being painted, as well as being used for * things like guidelines or searches. * @param recursive when true, checks visibility recursively * @return current visibility state of this shape. * @see isGeometryProtected(), isContentProtected(), isSelectable() */ bool isVisible(bool recursive = false) const; /** * Changes the shape to be printable or not. The default is true. * * If a Shape's print flag is true, the shape will be printed. If * false, the shape will not be printed. If a shape is not visible (@see isVisible), * it isPrinted will return false, too. */ void setPrintable(bool on); /** * Returns the current printable state of this shape. * * A shape can be visible but not printable, not printable and not visible * or visible and printable, but not invisible and still printable. * * @return current printable state of this shape. */ bool isPrintable() const; /** * Makes it possible for the user to select this shape. * This parameter defaults to true. * @param selectable when true; set the shape to be selectable by the user. * @see setGeometryProtected(), setContentProtected(), setVisible() */ void setSelectable(bool selectable); /** * Returns if this shape can be selected by the user. * @return true only when the object is selectable. * @see isGeometryProtected(), isContentProtected(), isVisible() */ bool isSelectable() const; /** * Tells the shape to have its position/rotation and size protected from user-changes. * The geometry being protected means the user can not change shape or position of the * shape. This includes any matrix operation such as rotation. * @param on when true; set the shape to have its geometry protected. * @see setContentProtected(), setSelectable(), setVisible() */ void setGeometryProtected(bool on); /** * Returns current geometry protection state of this shape. * The geometry being protected means the user can not change shape or position of the * shape. This includes any matrix operation such as rotation. * @return current geometry protection state of this shape. * @see isContentProtected(), isSelectable(), isVisible() */ bool isGeometryProtected() const; /** * Marks the shape to have its content protected against editing. * Content protection is a hint for tools to disallow the user editing the content. * @param protect when true set the shapes content to be protected from user modification. * @see setGeometryProtected(), setSelectable(), setVisible() */ void setContentProtected(bool protect); /** * Returns current content protection state of this shape. * Content protection is a hint for tools to disallow the user editing the content. * @return current content protection state of this shape. * @see isGeometryProtected(), isSelectable(), isVisible() */ bool isContentProtected() const; /** * Returns the parent, or 0 if there is no parent. * @return the parent, or 0 if there is no parent. */ KoShapeContainer *parent() const; /** * Set the parent of this shape. * @param parent the new parent of this shape. Can be 0 if the shape has no parent anymore. */ void setParent(KoShapeContainer *parent); /** * @brief inheritsTransformFromAny checks if the shape inherits transformation from * any of the shapes listed in \p ancestorsInQuestion. The inheritance is checked * in recursive way. * @return true if there is a (transitive) transformation-wise parent found in \p ancestorsInQuestion */ bool inheritsTransformFromAny(const QList ancestorsInQuestion) const; /** * Request a repaint to be queued. * The repaint will be of the entire Shape, including its selection handles should this * shape be selected. *

This method will return immediately and only request a repaint. Successive calls * will be merged into an appropriate repaint action. */ virtual void update() const; /** * Request a repaint to be queued. * The repaint will be restricted to the parameters rectangle, which is expected to be * in absolute coordinates of the canvas and it is expected to be * normalized. *

This method will return immediately and only request a repaint. Successive calls * will be merged into an appropriate repaint action. * @param rect the rectangle (in pt) to queue for repaint. */ virtual void updateAbsolute(const QRectF &rect) const; /// Used by compareShapeZIndex() to order shapes enum ChildZOrderPolicy { ChildZDefault, ChildZParentChild = ChildZDefault, ///< normal parent/child ordering ChildZPassThrough ///< children are considered equal to this shape }; /** * Returns if during compareShapeZIndex() how this shape portrays the values * of its children. The default behaviour is to let this shape's z values take * the place of its childrens values, so you get a parent/child relationship. * The children are naturally still ordered relatively to their z values * * But for special cases (like Calligra's TextShape) it can be overloaded to return * ChildZPassThrough which means the children keep their own z values * @returns the z order policy of this shape */ virtual ChildZOrderPolicy childZOrderPolicy(); /** * This is a method used to sort a list using the STL sorting methods. * @param s1 the first shape * @param s2 the second shape */ static bool compareShapeZIndex(KoShape *s1, KoShape *s2); /** * returns the outline of the shape in the form of a path. * The outline returned will always be relative to the position() of the shape, so * moving the shape will not alter the result. The outline is used to draw the stroke * on, for example. * @returns the outline of the shape in the form of a path. */ virtual QPainterPath outline() const; /** * returns the outline of the shape in the form of a rect. * The outlineRect returned will always be relative to the position() of the shape, so * moving the shape will not alter the result. The outline is used to calculate * the boundingRect. * @returns the outline of the shape in the form of a rect. */ virtual QRectF outlineRect() const; /** * returns the outline of the shape in the form of a path for the use of painting a shadow. * * Normally this would be the same as outline() if there is a fill (background) set on the * shape and empty if not. However, a shape could reimplement this to return an outline * even if no fill is defined. A typical example of this would be the picture shape * which has a picture but almost never a background. * * @returns the outline of the shape in the form of a path. */ virtual QPainterPath shadowOutline() const; /** * Returns the currently set stroke, or 0 if there is no stroke. * @return the currently set stroke, or 0 if there is no stroke. */ KoShapeStrokeModelSP stroke() const; /** * Set a new stroke, removing the old one. The stroke inheritance becomes disabled. * @param stroke the new stroke, or 0 if there should be no stroke. */ void setStroke(KoShapeStrokeModelSP stroke); /** * @brief setInheritStroke marks a shape as inhiriting the stroke * from the parent shape. NOTE: The currently selected stroke is destroyed. * @param value true if the shape should inherit the stroke style */ void setInheritStroke(bool value); /** * @brief inheritStroke shows if the shape inherits the stroke from its parent * @return true if the shape inherits the stroke style */ bool inheritStroke() const; /** * Return the insets of the stroke. * Convenience method for KoShapeStrokeModel::strokeInsets() */ KoInsets strokeInsets() const; /// Sets the new shadow, removing the old one void setShadow(KoShapeShadow *shadow); /// Returns the currently set shadow or 0 if there is no shadow set KoShapeShadow *shadow() const; /// Sets the new border, removing the old one. void setBorder(KoBorder *border); /// Returns the currently set border or 0 if there is no border set KoBorder *border() const; /// Sets a new clip path, removing the old one void setClipPath(KoClipPath *clipPath); /// Returns the currently set clip path or 0 if there is no clip path set KoClipPath * clipPath() const; /// Sets a new clip mask, removing the old one. The mask is owned by the shape. void setClipMask(KoClipMask *clipMask); /// Returns the currently set clip mask or 0 if there is no clip mask set KoClipMask* clipMask() const; /** * Setting the shape to keep its aspect-ratio has the effect that user-scaling will * keep the width/hight ratio intact so as not to distort shapes that rely on that * ratio. * @param keepAspect the new value */ void setKeepAspectRatio(bool keepAspect); /** * Setting the shape to keep its aspect-ratio has the effect that user-scaling will * keep the width/hight ratio intact so as not to distort shapes that rely on that * ratio. * @return whether to keep aspect ratio of this shape */ bool keepAspectRatio() const; /** * Return the position of this shape regardless of rotation/skew/scaling and regardless of * this shape having a parent (being in a group) or not.
* @param anchor The place on the (unaltered) shape that you want the position of. * @return the point that is the absolute, centered position of this shape. */ QPointF absolutePosition(KoFlake::AnchorPosition anchor = KoFlake::Center) const; /** * Move this shape to an absolute position where the end location will be the same * regardless of the shape's rotation/skew/scaling and regardless of this shape having * a parent (being in a group) or not.
* The newPosition is going to be the center of the shape. * This has the convenient effect that:

     shape->setAbsolutePosition(QPointF(0,0));
     shape->rotate(45);
Will result in the same visual position of the shape as the opposite:
     shape->rotate(45);
     shape->setAbsolutePosition(QPointF(0,0));
* @param newPosition the new absolute center of the shape. * @param anchor The place on the (unaltered) shape that you set the position of. */ void setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor = KoFlake::Center); /** * Set a data object on the shape to be used by an application. * This is specifically useful when a shape is created in a plugin and that data from that * shape should be accessible outside the plugin. * @param userData the new user data, or 0 to delete the current one. */ void setUserData(KoShapeUserData *userData); /** * Return the current userData. */ KoShapeUserData *userData() const; /** * Return the Id of this shape, identifying the type of shape by the id of the factory. * @see KoShapeFactoryBase::shapeId() * @return the id of the shape-type */ QString shapeId() const; /** * Set the Id of this shape. A shapeFactory is expected to set the Id at creation * so applications can find out what kind of shape this is. * @see KoShapeFactoryBase::shapeId() * @param id the ID from the factory that created this shape */ void setShapeId(const QString &id); /** * Create a matrix that describes all the transformations done on this shape. * * The absolute transformation is the combined transformation of this shape * and all its parents and grandparents. * * @param converter if not null, this method uses the converter to mark the right * offsets in the current view. */ QTransform absoluteTransformation(const KoViewConverter *converter) const; /** * Applies a transformation to this shape. * * The transformation given is relative to the global coordinate system, i.e. the document. * This is a convenience function to apply a global transformation to this shape. * @see applyTransformation * * @param matrix the transformation matrix to apply */ void applyAbsoluteTransformation(const QTransform &matrix); /** * Sets a new transformation matrix describing the local transformations on this shape. * @param matrix the new transformation matrix */ void setTransformation(const QTransform &matrix); /// Returns the shapes local transformation matrix QTransform transformation() const; /** * Applies a transformation to this shape. * * The transformation given is relative to the shape coordinate system. * * @param matrix the transformation matrix to apply */ void applyTransformation(const QTransform &matrix); /** * Copy all the settings from the parameter shape and apply them to this shape. * Settings like the position and rotation to visible and locked. The parent * is a notable exclusion. * @param shape the shape to use as original */ void copySettings(const KoShape *shape); /** * Convenience method that allows people implementing paint() to use the shape * internal coordinate system directly to paint itself instead of considering the * views zoom. * @param painter the painter to alter the zoom level of. * @param converter the converter for the current views zoom. */ static void applyConversion(QPainter &painter, const KoViewConverter &converter); /** * A convenience method that creates a handles helper with applying transformations at * the same time. Please note that you shouldn't save/restore additionally. All the work * on restoring original painter's transformations is done by the helper. */ static KisHandlePainterHelper createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius = 0.0); /** * @brief Transforms point from shape coordinates to document coordinates * @param point in shape coordinates * @return point in document coordinates */ QPointF shapeToDocument(const QPointF &point) const; /** * @brief Transforms rect from shape coordinates to document coordinates * @param rect in shape coordinates * @return rect in document coordinates */ QRectF shapeToDocument(const QRectF &rect) const; /** * @brief Transforms point from document coordinates to shape coordinates * @param point in document coordinates * @return point in shape coordinates */ QPointF documentToShape(const QPointF &point) const; /** * @brief Transform rect from document coordinates to shape coordinates * @param rect in document coordinates * @return rect in shape coordinates */ QRectF documentToShape(const QRectF &rect) const; /** * Returns the name of the shape. * @return the shapes name */ QString name() const; /** * Sets the name of the shape. * @param name the new shape name */ void setName(const QString &name); /** * Update the position of the shape in the tree of the KoShapeManager. */ void notifyChanged(); /** * A shape can be in a state that it is doing processing data like loading or text layout. * In this case it can be shown on screen probably partially but it should really not be printed * until it is fully done processing. * Warning! This method can be blocking for a long time * @param asynchronous If set to true the processing will can take place in a different thread and the * function will not block until the shape is finised. * In case of printing Flake will call this method from a non-main thread and only * start printing it when the in case of printing method returned. * If set to false the processing needs to be done synchronously and will * block until the result is finished. */ virtual void waitUntilReady(const KoViewConverter &converter, bool asynchronous = true) const; /// checks recursively if the shape or one of its parents is not visible or locked bool isEditable() const; /** * Adds a shape which depends on this shape. * Making a shape dependent on this one means it will get shapeChanged() called * on each update of this shape. * * If this shape already depends on the given shape, establishing the * dependency is refused to prevent circular dependencies. * * @param shape the shape which depends on this shape * @return true if dependency could be established, otherwise false * @see removeDependee(), hasDependee() */ bool addDependee(KoShape *shape); /** * Removes as shape depending on this shape. * @see addDependee(), hasDependee() */ void removeDependee(KoShape *shape); /// Returns if the given shape is dependent on this shape bool hasDependee(KoShape *shape) const; /// Returns list of shapes depending on this shape QList dependees() const; /// Returns additional snap data the shape wants to have snapping to virtual KoSnapData snapData() const; /** * Set additional attribute * * This can be used to attach additional attributes to a shape for attributes * that are application specific like presentation:placeholder * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * @param value The value of the attribute */ void setAdditionalAttribute(const QString &name, const QString &value); /** * Remove additional attribute * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder */ void removeAdditionalAttribute(const QString &name); /** * Check if additional attribute is set * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * * @return true if there is a attribute with prefix:tag set, false otherwise */ bool hasAdditionalAttribute(const QString &name) const; /** * Get additional attribute * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * * @return The value of the attribute if it exists or a null string if not found. */ QString additionalAttribute(const QString &name) const; void setAdditionalStyleAttribute(const char *name, const QString &value); void removeAdditionalStyleAttribute(const char *name); /** * Returns the filter effect stack of the shape * * @return the list of filter effects applied on the shape when rendering. */ KoFilterEffectStack *filterEffectStack() const; /// Sets the new filter effect stack, removing the old one void setFilterEffectStack(KoFilterEffectStack *filterEffectStack); /** * Set the property collision detection. * Setting this to true will result in calls to shapeChanged() with the CollisionDetected * parameter whenever either this or another shape is moved/rotated etc and intersects this shape. * @param detect if true detect collisions. */ void setCollisionDetection(bool detect); /** * get the property collision detection. * @returns true if collision detection is on. */ bool collisionDetection(); /** * Return the tool delegates for this shape. * In Flake a shape being selected will cause the tool manager to make available all tools that * can edit the selected shapes. In some cases selecting one shape should allow the tool to * edit a related shape be available too. The tool delegates allows this to happen by taking * all the shapes in the set into account on tool selection. * Notice that if the set is non-empty 'this' shape is no longer looked at. You can choose * to add itself to the set too. */ QSet toolDelegates() const; /** * Set the tool delegates. * @param delegates the new delegates. * @see toolDelegates() */ void setToolDelegates(const QSet &delegates); /** * Return the hyperlink for this shape. */ QString hyperLink () const; /** * Set hyperlink for this shape. * @param hyperLink name. */ void setHyperLink(const QString &hyperLink); /** * \internal * Returns the private object for use within the flake lib */ KoShapePrivate *priv(); public: - struct ShapeChangeListener { + struct KRITAFLAKE_EXPORT ShapeChangeListener { virtual ~ShapeChangeListener(); virtual void notifyShapeChanged(ChangeType type, KoShape *shape) = 0; private: friend class KoShape; friend class KoShapePrivate; void registerShape(KoShape *shape); void unregisterShape(KoShape *shape); void notifyShapeChangedImpl(ChangeType type, KoShape *shape); QList m_registeredShapes; }; void addShapeChangeListener(ShapeChangeListener *listener); void removeShapeChangeListener(ShapeChangeListener *listener); public: static QList linearizeSubtree(const QList &shapes); protected: /// constructor KoShape(KoShapePrivate *); /* ** loading saving helper methods */ /// attributes from ODF 1.1 chapter 9.2.15 Common Drawing Shape Attributes enum OdfAttribute { OdfTransformation = 1, ///< Store transformation information OdfSize = 2, ///< Store size information OdfPosition = 8, ///< Store position OdfAdditionalAttributes = 4, ///< Store additional attributes of the shape OdfCommonChildElements = 16, ///< Event actions and connection points OdfLayer = 64, ///< Store layer name OdfStyle = 128, ///< Store the style OdfId = 256, ///< Store the unique ID OdfName = 512, ///< Store the name of the shape OdfZIndex = 1024, ///< Store the z-index OdfViewbox = 2048, ///< Store the viewbox /// A mask for all mandatory attributes OdfMandatories = OdfLayer | OdfStyle | OdfId | OdfName | OdfZIndex, /// A mask for geometry attributes OdfGeometry = OdfPosition | OdfSize, /// A mask for all the attributes OdfAllAttributes = OdfTransformation | OdfGeometry | OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements }; /** * This method is used during loading of the shape to load common attributes * * @param context the KoShapeLoadingContext used for loading * @param element element which represents the shape in odf * @param attributes a number of OdfAttribute items to state which attributes to load. */ bool loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes); /** * Parses the transformation attribute from the given string * @param transform the transform attribute string * @return the resulting transformation matrix */ QTransform parseOdfTransform(const QString &transform); /** * @brief Saves the style used for the shape * * This method fills the given style object with the stroke and * background properties and then adds the style to the context. * * @param style the style object to fill * @param context used for saving * @return the name of the style * @see saveOdf */ virtual QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const; /** * Loads the stroke and fill style from the given element. * * @param element the xml element to load the style from * @param context the loading context used for loading */ virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context); /// Loads the stroke style KoShapeStrokeModelSP loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const; /// Loads the fill style QSharedPointer loadOdfFill(KoShapeLoadingContext &context) const; /// Loads the connection points void loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context); /// Loads the clip contour void loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor); /* ** end loading saving */ /** * A hook that allows inheriting classes to do something after a KoShape property changed * This is called whenever the shape, position rotation or scale properties were altered. * @param type an indicator which type was changed. */ virtual void shapeChanged(ChangeType type, KoShape *shape = 0); /// return the current matrix that contains the rotation/scale/position of this shape QTransform transform() const; KoShapePrivate *d_ptr; private: Q_DECLARE_PRIVATE(KoShape) }; Q_DECLARE_METATYPE(KoShape*) #endif diff --git a/libs/flake/svg/SvgUtil.h b/libs/flake/svg/SvgUtil.h index 217eeff2e9..22493ca9c2 100644 --- a/libs/flake/svg/SvgUtil.h +++ b/libs/flake/svg/SvgUtil.h @@ -1,150 +1,150 @@ /* This file is part of the KDE project * Copyright (C) 2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SVGUTIL_H #define SVGUTIL_H #include "kritaflake_export.h" #include class QString; class QTransform; class QStringList; class KoXmlWriter; #include class SvgGraphicsContext; class KRITAFLAKE_EXPORT SvgUtil { public: // remove later! pixels *are* user coordinates static double fromUserSpace(double value); static double toUserSpace(double value); static double ptToPx(SvgGraphicsContext *gc, double value); /// Converts given point from points to userspace units. static QPointF toUserSpace(const QPointF &point); /// Converts given rectangle from points to userspace units. static QRectF toUserSpace(const QRectF &rect); /// Converts given rectangle from points to userspace units. static QSizeF toUserSpace(const QSizeF &size); /** * Parses the given string containing a percentage number. * @param s the input string containing the percentage * @return the percentage number normalized to 0..100 */ static QString toPercentage(qreal value); /** * Parses the given string containing a percentage number. * @param s the input string containing the percentage * @return the percentage number normalized to 0..1 */ static double fromPercentage(QString s); /** * Converts position from objectBoundingBox units to userSpace units. */ static QPointF objectToUserSpace(const QPointF &position, const QRectF &objectBound); /** * Converts size from objectBoundingBox units to userSpace units. */ static QSizeF objectToUserSpace(const QSizeF &size, const QRectF &objectBound); /** * Converts position from userSpace units to objectBoundingBox units. */ static QPointF userSpaceToObject(const QPointF &position, const QRectF &objectBound); /** * Converts size from userSpace units to objectBoundingBox units. */ static QSizeF userSpaceToObject(const QSizeF &size, const QRectF &objectBound); /// Converts specified transformation to a string static QString transformToString(const QTransform &transform); /// Writes a \p transform as an attribute \p name iff the transform is not empty static void writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter); /// Parses a viewbox attribute into an rectangle static bool parseViewBox(SvgGraphicsContext *gc, const KoXmlElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform); struct PreserveAspectRatioParser; static void parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewRect, QTransform *_viewTransform); /// Parses a length attribute static qreal parseUnit(SvgGraphicsContext *gc, const QString &, bool horiz = false, bool vert = false, const QRectF &bbox = QRectF()); /// parses a length attribute in x-direction static qreal parseUnitX(SvgGraphicsContext *gc, const QString &unit); /// parses a length attribute in y-direction static qreal parseUnitY(SvgGraphicsContext *gc, const QString &unit); /// parses a length attribute in xy-direction static qreal parseUnitXY(SvgGraphicsContext *gc, const QString &unit); /// parses angle, result in *radians*! static qreal parseUnitAngular(SvgGraphicsContext *gc, const QString &unit); /// parses the number into parameter number static const char * parseNumber(const char *ptr, qreal &number); static qreal parseNumber(const QString &string); static QString mapExtendedShapeTag(const QString &tagName, const KoXmlElement &element); static QStringList simplifyList(const QString &str); - struct PreserveAspectRatioParser + struct KRITAFLAKE_EXPORT PreserveAspectRatioParser { PreserveAspectRatioParser(const QString &str); enum Alignment { Min, Middle, Max }; bool defer = false; Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio; Alignment xAlignment = Min; Alignment yAlignment = Min; QPointF rectAnchorPoint(const QRectF &rc) const; QString toString() const; private: Alignment alignmentFromString(const QString &str) const; QString alignmentToString(Alignment alignment) const; static qreal alignedValue(qreal min, qreal max, Alignment alignment); }; }; #endif // SVGUTIL_H diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 9b6ff83ee7..d494e04d77 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,401 +1,400 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/metadata ${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_CURRENT_SOURCE_DIR}/recorder ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} - ${Boost_INCLUDE_DIRS} ) 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/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 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 commands/kis_deselect_global_selection_command.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_layer_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/kis_set_global_selection_command.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 processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp filter/kis_filter.cc 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/kis_colorize_mask.cpp lazybrush/kis_colorize_stroke_strategy.cpp KisDelayedUpdateNodeInterface.cpp kis_adjustment_layer.cc kis_selection_based_layer.cpp kis_node_filter_interface.cpp kis_base_accessor.cpp kis_base_node.cpp kis_base_processor.cpp kis_bookmarked_configuration_manager.cc kis_clone_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 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 KisUpdateSchedulerConfigNotifier.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 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_thread_safe_signal_compressor.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 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 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 metadata/kis_meta_data_entry.cc metadata/kis_meta_data_filter.cc metadata/kis_meta_data_filter_p.cc metadata/kis_meta_data_filter_registry.cc metadata/kis_meta_data_filter_registry_model.cc metadata/kis_meta_data_io_backend.cc metadata/kis_meta_data_merge_strategy.cc metadata/kis_meta_data_merge_strategy_p.cc metadata/kis_meta_data_merge_strategy_registry.cc metadata/kis_meta_data_parser.cc metadata/kis_meta_data_schema.cc metadata/kis_meta_data_schema_registry.cc metadata/kis_meta_data_store.cc metadata/kis_meta_data_type_info.cc metadata/kis_meta_data_validator.cc metadata/kis_meta_data_value.cc recorder/kis_action_recorder.cc recorder/kis_macro.cc recorder/kis_macro_player.cc recorder/kis_node_query_path.cc recorder/kis_play_info.cc recorder/kis_recorded_action.cc recorder/kis_recorded_action_factory_registry.cc recorder/kis_recorded_action_load_context.cpp recorder/kis_recorded_action_save_context.cpp recorder/kis_recorded_filter_action.cpp recorder/kis_recorded_fill_paint_action.cpp recorder/kis_recorded_node_action.cc recorder/kis_recorded_paint_action.cpp recorder/kis_recorded_path_paint_action.cpp recorder/kis_recorded_shape_paint_action.cpp kis_keyframe.cpp kis_keyframe_channel.cpp kis_keyframe_commands.cpp kis_scalar_keyframe_channel.cpp kis_raster_keyframe_channel.cpp kis_onion_skin_compositor.cpp kis_onion_skin_cache.cpp kis_idle_watcher.cpp kis_psd_layer_style.cpp kis_layer_properties_icons.cpp layerstyles/kis_multiple_projection.cpp layerstyles/kis_layer_style_filter.cpp layerstyles/kis_layer_style_filter_environment.cpp layerstyles/kis_layer_style_filter_projection_plane.cpp layerstyles/kis_layer_style_projection_plane.cpp layerstyles/kis_ls_drop_shadow_filter.cpp layerstyles/kis_ls_satin_filter.cpp layerstyles/kis_ls_stroke_filter.cpp layerstyles/kis_ls_bevel_emboss_filter.cpp layerstyles/kis_ls_overlay_filter.cpp layerstyles/kis_ls_utils.cpp layerstyles/gimp_bump_map.cpp KisProofingConfiguration.cpp ) 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 Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) 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}) ########### install schemas ############# install( FILES metadata/schemas/dc.schema metadata/schemas/exif.schema metadata/schemas/tiff.schema metadata/schemas/mkn.schema metadata/schemas/xmp.schema metadata/schemas/xmpmm.schema metadata/schemas/xmprights.schema DESTINATION ${DATA_INSTALL_DIR}/krita/metadata/schemas) diff --git a/libs/image/brushengine/kis_paintop_preset.h b/libs/image/brushengine/kis_paintop_preset.h index 8a7e5c1ff7..582e2292fc 100644 --- a/libs/image/brushengine/kis_paintop_preset.h +++ b/libs/image/brushengine/kis_paintop_preset.h @@ -1,138 +1,138 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_PRESET_H #define KIS_PAINTOP_PRESET_H #include #include "KoID.h" #include "kis_types.h" #include "kis_shared.h" #include "kritaimage_export.h" #include class KisPaintopSettingsUpdateProxy; class KisPaintOpConfigWidget; /** * A KisPaintOpPreset contains a particular set of settings * associated with a paintop, like brush, paintopsettings. * A new property in this class is to make it dirty. That means the * user can now temporarily save any tweaks in the Preset throughout * the session. The Dirty Preset setting/unsetting is handled by KisPaintOpPresetSettings */ class KRITAIMAGE_EXPORT KisPaintOpPreset : public KoResource, public KisShared { public: KisPaintOpPreset(); KisPaintOpPreset(const QString& filename); ~KisPaintOpPreset() override; KisPaintOpPresetSP clone() const; /// set the id of the paintop plugin void setPaintOp(const KoID & paintOp); /// return the id of the paintop plugin KoID paintOp() const; /// replace the current settings object with the specified settings void setSettings(KisPaintOpSettingsSP settings); void setOriginalSettings(KisPaintOpSettingsSP originalSettings); /// return the settings that define this paintop preset KisPaintOpSettingsSP settings() const; KisPaintOpSettingsSP originalSettings() const; bool load() override; bool loadFromDevice(QIODevice *dev) override; bool save() override; bool saveToDevice(QIODevice* dev) const override; void toXML(QDomDocument& doc, QDomElement& elt) const; void fromXML(const QDomElement& elt); bool removable() const { return true; } QString defaultFileExtension() const override { return ".kpp"; } void setPresetDirty(bool value); bool isPresetDirty() const; /** * Never use manual save/restore calls to * isPresetDirty()/setPresetDirty()! They will lead to * hard-to-tack-down bugs when the dirty state will not be * restored on jumps like 'return', 'break' or exception. */ - class DirtyStateSaver { + class KRITAIMAGE_EXPORT DirtyStateSaver { public: DirtyStateSaver(KisPaintOpPreset *preset) : m_preset(preset), m_isDirty(preset->isPresetDirty()) { } ~DirtyStateSaver() { m_preset->setPresetDirty(m_isDirty); } private: KisPaintOpPreset *m_preset; bool m_isDirty; }; /** * @brief The UpdatedPostponer class * @see KisPaintopSettingsUpdateProxy::postponeSettingsChanges() */ - class UpdatedPostponer{ + class KRITAIMAGE_EXPORT UpdatedPostponer{ public: UpdatedPostponer(KisPaintOpPreset *preset); ~UpdatedPostponer(); private: KisPaintopSettingsUpdateProxy *m_updateProxy; }; void setOptionsWidget(KisPaintOpConfigWidget *widget); KisPaintopSettingsUpdateProxy* updateProxy() const; KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const; QList uniformProperties(); private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisPaintOpPresetSP) #endif diff --git a/libs/image/kis_scalar_keyframe_channel.h b/libs/image/kis_scalar_keyframe_channel.h index 4ee6a653eb..23b5141ac0 100644 --- a/libs/image/kis_scalar_keyframe_channel.h +++ b/libs/image/kis_scalar_keyframe_channel.h @@ -1,70 +1,70 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_SCALAR_KEYFRAME_CHANNEL_H #define _KIS_SCALAR_KEYFRAME_CHANNEL_H #include "kis_keyframe_channel.h" #include "kis_keyframe_commands.h" class KRITAIMAGE_EXPORT KisScalarKeyframeChannel : public KisKeyframeChannel { Q_OBJECT public: - struct AddKeyframeCommand : public KisReplaceKeyframeCommand + struct KRITAIMAGE_EXPORT AddKeyframeCommand : public KisReplaceKeyframeCommand { AddKeyframeCommand(KisScalarKeyframeChannel *channel, int time, qreal value, KUndo2Command *parentCommand); }; KisScalarKeyframeChannel(const KoID& id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds, KisKeyframe::InterpolationMode defaultInterpolation=KisKeyframe::Constant); KisScalarKeyframeChannel(const KisScalarKeyframeChannel &rhs, KisNode *newParentNode); ~KisScalarKeyframeChannel() override; bool hasScalarValue() const override; qreal minScalarValue() const override; qreal maxScalarValue() const override; qreal scalarValue(const KisKeyframeSP keyframe) const override; void setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand = 0) override; void setInterpolationMode(KisKeyframeSP keyframe, KisKeyframe::InterpolationMode mode, KUndo2Command *parentCommand = 0); void setInterpolationTangents(KisKeyframeSP keyframe, KisKeyframe::InterpolationTangentsMode, QPointF leftTangent, QPointF rightTangent, KUndo2Command *parentCommand); qreal interpolatedValue(int time) const; qreal currentValue() const; static QPointF interpolate(QPointF point1, QPointF rightTangent, QPointF leftTangent, QPointF point2, qreal t); protected: KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) override; KisKeyframeSP createKeyframe(int time, qreal value, KUndo2Command *parentCommand); void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) override; void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) override; QRect affectedRect(KisKeyframeSP key) override; void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) override; KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) override; private: void notifyKeyframeChanged(KisKeyframeSP keyframe); struct Private; QScopedPointer m_d; }; #endif diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index 280bd167f5..6c536c8163 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,242 +1,241 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( ${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} - ${Boost_INCLUDE_DIRS} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_change_profile_visitor_test.cpp kis_clone_layer_test.cpp kis_colorspace_convert_visitor_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_macro_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_recorded_action_factory_registry_test.cpp kis_recorded_action_test.cpp kis_recorded_filter_action_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_node_query_path_test.cpp kis_fixed_point_maths_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp NAME_PREFIX "krita-image-" LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_layer_style_filter_environment_test.cpp TEST_NAME kritaimage-layer_style_filter_environment_test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) ecm_add_test(kis_asl_parser_test.cpp TEST_NAME kritalibpsd-asl_parser_test LINK_LIBRARIES kritapsd kritapigment kritawidgetutils kritacommand Qt5::Xml Qt5::Test) ecm_add_test(KisPerStrokeRandomSourceTest.cpp TEST_NAME KisPerStrokeRandomSourceTest LINK_LIBRARIES kritaimage Qt5::Test) # ecm_add_test(kis_dom_utils_test.cpp # TEST_NAME krita-image-DomUtils-Test # LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc dep # kis_transform_worker_test.cpp # TEST_NAME krita-image-KisTransformWorkerTest #LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc # kis_perspective_transform_worker_test.cpp # TEST_NAME krita-image-KisPerspectiveTransformWorkerTest #LINK_LIBRARIES kritaimage Qt5::Test) # kis_cs_conversion_test.cpp # TEST_NAME krita-image-KisCsConversionTest # LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc # kis_processings_test.cpp # TEST_NAME krita-image-KisProcessingsTest #LINK_LIBRARIES kritaimage Qt5::Test) # image/tests cannot use stuff that needs kisdocument # kis_projection_leaf_test.cpp # TEST_NAME kritaimage-projection_leaf_test # LINK_LIBRARIES kritaimage Qt5::Test) if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_paint_device_test.cpp TEST_NAME krita-image-KisPaintDeviceTest LINK_LIBRARIES kritaimage kritaodf Qt5::Test) else() message(WARNING "Skipping KisPaintDeviceTest!!!!!!!!!!!!!!") endif() if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_filter_mask_test.cpp TEST_NAME krita-image-KisFilterMaskTest LINK_LIBRARIES kritaimage Qt5::Test) else() message(WARNING "Skipping KisFilterMaskTest!!!!!!!!!!!!!!") endif() krita_add_broken_unit_test(kis_transform_mask_test.cpp TEST_NAME krita-image-KisTransformMaskTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_histogram_test.cpp TEST_NAME krita-image-KisHistogramTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_walkers_test.cpp TEST_NAME krita-image-KisWalkersTest LINK_LIBRARIES kritaimage Qt5::Test) #krita_add_broken_unit_test(kis_async_merger_test.cpp # TEST_NAME krita-image-KisAsyncMergerTest # LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_update_scheduler_test.cpp TEST_NAME krita-image-KisUpdateSchedulerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_queues_progress_updater_test.cpp TEST_NAME krita-image-KisQueuesProgressUpdaterTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_cage_transform_worker_test.cpp TEST_NAME krita-image-KisCageTransformWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_meta_data_test.cpp TEST_NAME krita-image-KisMetaDataTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_random_generator_test.cpp TEST_NAME krita-image-KisRandomGeneratorTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_keyframing_test.cpp TEST_NAME krita-image-Keyframing-Test LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_image_animation_interface_test.cpp TEST_NAME krita-image-ImageAnimationInterface-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_onion_skin_compositor_test.cpp TEST_NAME krita-image-OnionSkinCompositor-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_layer_styles_test.cpp TEST_NAME krita-image-LayerStylesTest LINK_LIBRARIES kritaimage Qt5::Test) diff --git a/libs/image/tiles3/CMakeLists.txt b/libs/image/tiles3/CMakeLists.txt index 4e7c26ef58..88c0516360 100644 --- a/libs/image/tiles3/CMakeLists.txt +++ b/libs/image/tiles3/CMakeLists.txt @@ -1,2 +1 @@ -include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) add_subdirectory(tests) diff --git a/libs/libqml/CMakeLists.txt b/libs/libqml/CMakeLists.txt index 2560c1f04f..7e3b996efb 100644 --- a/libs/libqml/CMakeLists.txt +++ b/libs/libqml/CMakeLists.txt @@ -1,46 +1,45 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) include_directories(SYSTEM - ${Boost_INCLUDE_DIRS} ${EIGEN3_INCLUDE_DIR} ) add_subdirectory(plugins) set(kritaqml_SRCS DocumentManager.cpp DocumentListModel.cpp KisSelectionExtras.cpp RecentFileManager.cpp ProgressProxy.cpp PropertyContainer.cpp Settings.cpp VirtualKeyboardController.cpp Theme.cpp QmlGlobalEngine.cpp KisSketchView.cpp ) qt5_add_resources(kritaqml_SRCS qml/qml.qrc) add_library(kritaqml SHARED ${kritaqml_SRCS}) generate_export_header(kritaqml BASE_NAME krita_sketch) set_target_properties(kritaqml PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) target_link_libraries(kritaqml Qt5::Quick Qt5::Gui Qt5::Core kritawidgets kritaui ) include(GNUInstallDirs) install(TARGETS kritaqml ${INSTALL_TARGETS_DEFAULT_ARGS}) install(DIRECTORY qmlthemes DESTINATION ${DATA_INSTALL_DIR}/krita) diff --git a/libs/pigment/CMakeLists.txt b/libs/pigment/CMakeLists.txt index b1552b6da6..9525f9ee82 100644 --- a/libs/pigment/CMakeLists.txt +++ b/libs/pigment/CMakeLists.txt @@ -1,124 +1,121 @@ project(kritapigment) # we have to repeat platform specifics from top-level if (WIN32) include_directories(${CMAKE_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif () include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_SOURCE_DIR}/compositeops) -include_directories(SYSTEM - ${Boost_INCLUDE_DIRS} -) set(FILE_OPENEXR_SOURCES) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() set(LINK_VC_LIB) if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR}) set(LINK_VC_LIB ${Vc_LIBRARIES}) ko_compile_for_all_implementations_no_scalar(__per_arch_factory_objs compositeops/KoOptimizedCompositeOpFactoryPerArch.cpp) message("Following objects are generated from the per-arch lib") message(${__per_arch_factory_objs}) endif() add_subdirectory(tests) add_subdirectory(benchmarks) set(kritapigment_SRCS DebugPigment.cpp KoBasicHistogramProducers.cpp KoColor.cpp KoColorDisplayRendererInterface.cpp KoColorConversionAlphaTransformation.cpp KoColorConversionCache.cpp KoColorConversions.cpp KoColorConversionSystem.cpp KoColorConversionTransformation.cpp KoColorProofingConversionTransformation.cpp KoColorConversionTransformationFactory.cpp KoColorModelStandardIds.cpp KoColorProfile.cpp KoColorSpace.cpp KoColorSpaceEngine.cpp KoColorSpaceFactory.cpp KoColorSpaceMaths.cpp KoColorSpaceRegistry.cpp KoColorProfileStorage.cpp KoColorTransformation.cpp KoColorTransformationFactory.cpp KoColorTransformationFactoryRegistry.cpp KoCompositeColorTransformation.cpp KoCompositeOp.cpp KoCompositeOpRegistry.cpp KoCopyColorConversionTransformation.cpp KoFallBackColorTransformation.cpp KoHistogramProducer.cpp KoMultipleColorConversionTransformation.cpp KoUniqueNumberForIdServer.cpp colorspaces/KoAlphaColorSpace.cpp colorspaces/KoLabColorSpace.cpp colorspaces/KoRgbU16ColorSpace.cpp colorspaces/KoRgbU8ColorSpace.cpp colorspaces/KoSimpleColorSpaceEngine.cpp compositeops/KoOptimizedCompositeOpFactory.cpp compositeops/KoOptimizedCompositeOpFactoryPerArch_Scalar.cpp ${__per_arch_factory_objs} colorprofiles/KoDummyColorProfile.cpp resources/KoAbstractGradient.cpp resources/KoColorSet.cpp resources/KoPattern.cpp resources/KoResource.cpp resources/KoMD5Generator.cpp resources/KoHashGeneratorProvider.cpp resources/KoStopGradient.cpp resources/KoSegmentGradient.cpp ) set (EXTRA_LIBRARIES ${LINK_OPENEXR_LIB} ${LINK_VC_LIB}) if(MSVC OR (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")) # avoid "cannot open file 'LIBC.lib'" error set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:LIBC.LIB") endif() add_library(kritapigment SHARED ${kritapigment_SRCS}) generate_export_header(kritapigment) target_include_directories( kritapigment PUBLIC $ $ ) target_link_libraries( kritapigment PUBLIC kritaplugin kritastore kritaglobal ${EXTRA_LIBRARIES} KF5::I18n KF5::ConfigCore Qt5::Core Qt5::Gui Qt5::Xml ${WIN32_PLATFORM_NET_LIBS} ) set_target_properties(kritapigment PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritapigment ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/psd/CMakeLists.txt b/libs/psd/CMakeLists.txt index 9fcdb6eb4e..611cbf52f6 100644 --- a/libs/psd/CMakeLists.txt +++ b/libs/psd/CMakeLists.txt @@ -1,36 +1,35 @@ include_directories( ${CMAKE_BINARY_DIR}/libs/psd #For kispsd_include.h - ${Boost_INCLUDE_DIRS} ) set(kritapsd_LIB_SRCS psd_utils.cpp psd.cpp compression.cpp psd_pattern.cpp asl/kis_asl_reader.cpp asl/kis_asl_reader_utils.cpp asl/kis_asl_xml_parser.cpp asl/kis_asl_object_catcher.cpp asl/kis_asl_callback_object_catcher.cpp asl/kis_asl_xml_writer.cpp asl/kis_asl_writer_utils.cpp asl/kis_asl_patterns_writer.cpp asl/kis_asl_writer.cpp ) add_library(kritapsd SHARED ${kritapsd_LIB_SRCS} ) generate_export_header(kritapsd BASE_NAME kritapsd) if (WIN32) target_link_libraries(kritapsd kritapigment kritaglobal KF5::I18n ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${WIN32_PLATFORM_NET_LIBS}) else (WIN32) target_link_libraries(kritapsd kritapigment kritaglobal KF5::I18n ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY}) endif (WIN32) set_target_properties(kritapsd PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritapsd ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 30d7be8d73..dc9b264b29 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,580 +1,579 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} - ${Boost_INCLUDE_DIRS} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/kis_dlg_internal_color_selector.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc kis_base_option.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_config_notifier.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp kis_painting_assistants_manager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp kis_script_manager.cpp kis_resource_server_provider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp kis_view_plugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp kis_fps_decoration.cpp recorder/kis_node_query_path_editor.cc recorder/kis_recorded_action_creator.cc recorder/kis_recorded_action_creator_factory.cc recorder/kis_recorded_action_creator_factory_registry.cc recorder/kis_recorded_action_editor_factory.cc recorder/kis_recorded_action_editor_factory_registry.cc recorder/kis_recorded_filter_action_editor.cc recorder/kis_recorded_filter_action_creator.cpp recorder/kis_recorded_paint_action_editor.cc tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_recording_adapter.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_gradient_slider_widget.cc widgets/kis_gradient_slider.cpp widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_popup_button.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/squeezedcombobox.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_spinbox_color_selector.cpp widgets/kis_screen_color_picker.cpp widgets/kis_preset_live_preview_view.cpp widgets/KoDualColorButton.cpp widgets/kis_color_input.cpp widgets/kis_color_button.cpp widgets/KisVisualColorSelector.cpp widgets/KisVisualColorSelectorShape.cpp widgets/KisVisualEllipticalSelectorShape.cpp widgets/KisVisualRectangleSelectorShape.cpp widgets/KisVisualTriangleSelectorShape.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KoShapeFillWrapper.cpp utils/kis_document_aware_spin_box_unit_manager.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoStackAction.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisPaletteModel.cpp kis_palette_delegate.cpp kis_palette_view.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp ) if(WIN32) if (NOT Qt5Gui_PRIVATE_INCLUDE_DIRS) message(FATAL_ERROR "Qt5Gui Private header are missing!") endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp input/wintab/kis_tablet_support_win8.cpp opengl/kis_opengl_win.cpp ) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp ) if(UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support.cpp qtlockedfile/qtlockedfile_unix.cpp ) if(NOT APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_x11.cpp input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgpaintactioneditor.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui brushhud/kis_dlg_brush_hud_config.ui forms/wdgdlginternalcolorselector.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index e1661fa781..5878fe0d0c 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1669 +1,1674 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)), // deleted by QObject importExportManager(new KisImportExportManager(q)), // deleted manually undoStack(new UndoStack(q)), // deleted by QObject m_bAutoDetectedMime(false), modified(false), readwrite(true), firstMod(QDateTime::currentDateTime()), lastMod(firstMod), nserver(new KisNameServer(1)), imageIdleWatcher(2000 /*ms*/), savingLock(&savingMutex) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)), unit(rhs.unit), importExportManager(new KisImportExportManager(q)), mimeType(rhs.mimeType), outputMimeType(rhs.outputMimeType), undoStack(new UndoStack(q)), guidesConfig(rhs.guidesConfig), m_bAutoDetectedMime(rhs.m_bAutoDetectedMime), m_url(rhs.m_url), m_file(rhs.m_file), modified(rhs.modified), readwrite(rhs.readwrite), firstMod(rhs.firstMod), lastMod(rhs.lastMod), nserver(new KisNameServer(*rhs.nserver)), preActivatedNode(0), // the node is from another hierarchy! imageIdleWatcher(2000 /*ms*/), assistants(rhs.assistants), // WARNING: assistants should not store pointers to the document! gridConfig(rhs.gridConfig), savingLock(&savingMutex) { } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true)); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer.disconnect(this); d->autoSaveTimer.stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg; if (cfg.backupFile() && filePathInfo.exists()) { KBackup::backupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; return exportDocumentImpl(ExportFileJob(url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { + if (status == KisImportExportFilter::UserCancelled) + return; + const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error mesage", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage)); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { d->undoStack->setClean(); } setRecovered(false); removeAutoSaveFiles(); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->importExportManager->batchMode(); } void KisDocument::setFileBatchMode(const bool batchMode) { d->importExportManager->setBatchMode(batchMode); } KisDocument* KisDocument::lockAndCloneForSaving() { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument(lockAndCloneForSaving()); // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSave() { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); bool started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0); if (!started) { const int emergencyAutoSaveInterval = 10; // sec setAutoSaveDelay(emergencyAutoSaveInterval); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { const int emergencyAutoSaveInterval = 10; // sec setAutoSaveDelay(emergencyAutoSaveInterval); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error mesage", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg; d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer.stop(); // until the next change } else { setAutoSaveDelay(d->autoSaveDelay); // restart the timer } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } + KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, + initializationStatus, showWarnings, exportConfiguration); - if (d->childSavingFuture.isCanceled()) { + if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); - emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, this->errorMessage()); + emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setAutoSaveDelay(d->autoSaveDelay); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { //qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay; d->autoSaveDelay = delay; if (isReadWrite() && d->autoSaveDelay > 0) { d->autoSaveTimer.start(d->autoSaveDelay * 1000); } else { d->autoSaveTimer.stop(); } } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

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

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window && window->viewManager()) { KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified() { d->modified = true; } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles() { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg; d->undoStack->setUndoLimit(cfg.undoStackLimit()); setAutoSaveDelay(cfg.autoSaveInterval()); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisConfig cfg; KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); if (name != i18n("Unnamed") && !name.isEmpty()) { setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + '/' + name + ".kra")); } documentInfo()->setAboutInfo("abstract", description); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); Q_CHECK_PTR(layer); if (backgroundAsLayer) { image->setDefaultProjectionColor(KoColor(cs)); if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) { layer->paintDevice()->setDefaultPixel(bgColor); } else { // Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel KisFillPainter painter; painter.begin(layer->paintDevice()); painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8()); } } else { image->setDefaultProjectionColor(bgColor); } layer->setDirty(QRect(0, 0, width, height)); image->addNode(layer.data(), image->rootLayer().data()); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } KoShapeBasedDocumentBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList value) { d->assistants = value; } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image) { if (d->image) { // Disconnect existing sig/slot connections d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); d->image->initialRefreshGraph(); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index 2279ee8b03..820b290c24 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,618 +1,623 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisImportExportFilter.h" #include "KisDocument.h" #include #include #include "kis_painter.h" #include "kis_guides_config.h" #include "kis_grid_config.h" #include "kis_popup_button.h" #include #include "kis_async_action_feedback.h" // static cache for import and export mimetypes QStringList KisImportExportManager::m_importMimeTypes; QStringList KisImportExportManager::m_exportMimeTypes; class Q_DECL_HIDDEN KisImportExportManager::Private { public: bool batchMode {false}; KoUpdaterPtr updater; QString cachedExportFilterMimeType; QSharedPointer cachedExportFilter; }; struct KisImportExportManager::ConversionResult { ConversionResult() { } ConversionResult(const QFuture &futureStatus) : m_isAsync(true), m_futureStatus(futureStatus) { } ConversionResult(KisImportExportFilter::ConversionStatus status) : m_isAsync(false), m_status(status) { } bool isAsync() const { return m_isAsync; } QFuture futureStatus() const { // if the result is not async, then it means some failure happened, // just return a cancelled future KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || m_status != KisImportExportFilter::OK); return m_futureStatus; } KisImportExportFilter::ConversionStatus status() const { return m_status; } + void setStatus(KisImportExportFilter::ConversionStatus value) { + m_status = value; + } private: bool m_isAsync = false; QFuture m_futureStatus; KisImportExportFilter::ConversionStatus m_status = KisImportExportFilter::UsageError; }; KisImportExportManager::KisImportExportManager(KisDocument* document) : m_document(document) , d(new Private) { } KisImportExportManager::~KisImportExportManager() { delete d; } KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType) { ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); return result.status(); } KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); return result.status(); } -QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() || result.status() != KisImportExportFilter::OK, QFuture()); + status = result.status(); return result.futureStatus(); } // The static method to figure out to which parts of the // graph this mimetype has a connection to. QStringList KisImportExportManager::mimeFilter(Direction direction) { // Find the right mimetype by the extension QSet mimeTypes; // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; if (direction == KisImportExportManager::Import) { if (m_importMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_importMimeTypes = mimeTypes.toList(); } return m_importMimeTypes; } else if (direction == KisImportExportManager::Export) { if (m_exportMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_exportMimeTypes = mimeTypes.toList(); } return m_exportMimeTypes; } return QStringList(); } KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) { int weight = -1; KisImportExportFilter *filter = 0; QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } KisImportExportFilter *f = qobject_cast(obj); if (!f) { delete obj; continue; } int w = json.value("X-KDE-Weight").toInt(); if (w > weight) { delete filter; filter = f; f->setObjectName(loader->fileName()); weight = w; } } } qDeleteAll(list); if (filter) { filter->setMimeType(mimetype); } return filter; } void KisImportExportManager::setBatchMode(const bool batch) { d->batchMode = batch; } bool KisImportExportManager::batchMode(void) const { return d->batchMode; } void KisImportExportManager::setUpdater(KoUpdaterPtr updater) { d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@titile:window", "Open Audio")); return dialog.filename(); } KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true); } QSharedPointer filter; /** * Fetching a filter from the registry is a really expensive operation, * because it blocks all the threads. Cache the filter if possible. */ if (direction == KisImportExportManager::Export && d->cachedExportFilter && d->cachedExportFilterMimeType == typeName) { filter = d->cachedExportFilter; } else { filter = toQShared(filterForMimeType(typeName, direction)); if (direction == Export) { d->cachedExportFilter = filter; d->cachedExportFilterMimeType = typeName; } } if (!filter) { return KisImportExportFilter::FilterCreationError; } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); if (!d->updater.isNull()) { // WARNING: The updater is not guaranteed to be persistent! If you ever want // to add progress reporting to "Save also as .kra", make sure you create // a separate KoProgressUpdater for that! // WARNING2: the failsafe completion of the updater happens in the destructor // the filter. filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, KisImportExportFilter::BadConversionGraph); ConversionResult result = KisImportExportFilter::OK; if (direction == Import) { // async importing is not yet supported! KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); if (0 && !batchMode()) { KisAsyncActionFeedback f(i18n("Opening document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); } else { result = doImport(location, filter); } } else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; if (!batchMode() && !askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra)) { return KisImportExportFilter::UserCancelled; } if (isAsync) { result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration) { KisConfig().setExportConfiguration(typeName, exportConfiguration); } } + result.setStatus(KisImportExportFilter::OK); return result; } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) { // Fill with some meta information about the image KisImageSP image = m_document->image(); KisPaintDeviceSP dev = image->projection(); const KoColorSpace* cs = dev->colorSpace(); const bool isThereAlpha = KisPainter::checkDeviceHasTransparency(image->projection()); exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); const bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10"))); exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); } bool KisImportExportManager::askUserAboutExportConfiguration( QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, const bool batchMode, const bool showWarnings, bool *alsoAsKra) { const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); QStringList warnings; QStringList errors; { KisPreExportChecker checker; checker.check(m_document->image(), filter->exportChecks()); warnings = checker.warnings(); errors = checker.errors(); } KisConfigWidget *wdg = filter->createConfigurationWidget(0, from, to); // Extra checks that cannot be done by the checker, because the checker only has access to the image. if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved.")); } if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved.")); } if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved.")); } if (!batchMode && !errors.isEmpty()) { QString error = "

" + i18n("Error: cannot save this image as a %1.", mimeUserDescription) + " Reasons:

" + "

    "; Q_FOREACH(const QString &w, errors) { error += "\n
  • " + w + "
  • "; } error += "
"; QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error); return false; } if (!batchMode && (wdg || !warnings.isEmpty())) { KoDialog dlg; dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); dlg.setWindowTitle(mimeUserDescription); QWidget *page = new QWidget(&dlg); QVBoxLayout *layout = new QVBoxLayout(page); if (showWarnings && !warnings.isEmpty()) { QHBoxLayout *hLayout = new QHBoxLayout(); QLabel *labelWarning = new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hLayout->addWidget(labelWarning); KisPopupButton *bn = new KisPopupButton(0); bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription)); hLayout->addWidget(bn); layout->addLayout(hLayout); QTextBrowser *browser = new QTextBrowser(); browser->setMinimumWidth(bn->width()); bn->setPopupWidget(browser); QString warning = "

" + i18n("You will lose information when saving this image as a %1.", mimeUserDescription); if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); } if (wdg) { QGroupBox *box = new QGroupBox(i18n("Options")); QVBoxLayout *boxLayout = new QVBoxLayout(box); wdg->setConfiguration(exportConfiguration); boxLayout->addWidget(wdg); layout->addWidget(box); } QCheckBox *chkAlsoAsKra = 0; if (showWarnings && !warnings.isEmpty()) { chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); chkAlsoAsKra->setChecked(KisConfig().readEntry("AlsoSaveAsKra", false)); layout->addWidget(chkAlsoAsKra); } dlg.setMainWidget(page); dlg.resize(dlg.minimumSize()); if (showWarnings || wdg) { if (!dlg.exec()) { return false; } } *alsoAsKra = false; if (chkAlsoAsKra) { KisConfig().writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); *alsoAsKra = chkAlsoAsKra->isChecked(); } if (wdg) { *exportConfiguration = *wdg->configuration(); } } return true; } KisImportExportFilter::ConversionStatus KisImportExportManager::doImport(const QString &location, QSharedPointer filter) { QFile file(location); if (!file.exists()) { return KisImportExportFilter::FileNotFound; } if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { return KisImportExportFilter::FileNotFound; } KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); if (file.isOpen()) { file.close(); } return status; } KisImportExportFilter::ConversionStatus KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) { KisImportExportFilter::ConversionStatus status = doExportImpl(location, filter, exportConfiguration); if (alsoAsKra && status == KisImportExportFilter::OK) { QString kraLocation = location + ".kra"; QByteArray mime = m_document->nativeFormatMimeType(); QSharedPointer filter( filterForMimeType(QString::fromLatin1(mime), Export)); KIS_SAFE_ASSERT_RECOVER_NOOP(filter); if (filter) { filter->setFilename(kraLocation); KisPropertiesConfigurationSP kraExportConfiguration = filter->lastSavedConfiguration(mime, mime); status = doExportImpl(kraLocation, filter, kraExportConfiguration); } else { status = KisImportExportFilter::FilterCreationError; } } return status; } KisImportExportFilter::ConversionStatus KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) { QSaveFile file(location); file.setDirectWriteFallback(true); if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { file.cancelWriting(); return KisImportExportFilter::CreationError; } KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, exportConfiguration); if (status != KisImportExportFilter::OK) { file.cancelWriting(); } else { file.commit(); } return status; } #include diff --git a/libs/ui/KisImportExportManager.h b/libs/ui/KisImportExportManager.h index 600ded4ab2..275b57bbce 100644 --- a/libs/ui/KisImportExportManager.h +++ b/libs/ui/KisImportExportManager.h @@ -1,161 +1,161 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_IMPORT_EXPORT_MANAGER_H #define KIS_IMPORT_EXPORT_MANAGER_H #include #include #include #include #include "KisImportExportFilter.h" #include "kritaui_export.h" class KisDocument; class KoProgressUpdater; template class QFuture; /** * @brief The class managing all the filters. * * This class manages all filters for a %Calligra application. Normally * you will not have to use it, since KisMainWindow takes care of loading * and saving documents. * * @ref KisFilter * * @author Kalle Dalheimer * @author Torben Weis * @author Werner Trobin */ class KRITAUI_EXPORT KisImportExportManager : public QObject { Q_OBJECT public: /** * This enum is used to distinguish the import/export cases */ enum Direction { Import = 1, Export = 2 }; /** * Create a filter manager for a document */ explicit KisImportExportManager(KisDocument *document); public: ~KisImportExportManager() override; /** * Imports the specified document and returns the resultant filename * (most likely some file in /tmp). * @p path can be either a URL or a filename. * @p documentMimeType gives importDocument a hint about what type * the document may be. It can be left empty. * * @return status signals the success/error of the conversion. * If the QString which is returned isEmpty() and the status is OK, * then we imported the file directly into the document. */ KisImportExportFilter::ConversionStatus importDocument(const QString &location, const QString &mimeType); /** * @brief Exports the given file/document to the specified URL/mimetype. * * If @p mimeType is empty, then the closest matching Calligra part is searched * and when the method returns @p mimeType contains this mimetype. * Oh, well, export is a C++ keyword ;) */ KisImportExportFilter::ConversionStatus exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); - QFuture exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); + QFuture exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); ///@name Static API //@{ /** * Suitable for passing to KoFileDialog::setMimeTypeFilters. The default mime * gets set by the "users" of this method, as we do not have enough * information here. * Optionally, @p extraNativeMimeTypes are added after the native mimetype. */ static QStringList mimeFilter(Direction direction); /** * @brief filterForMimeType loads the relevant import/export plugin and returns it. The caller * is responsible for deleting it! * @param mimetype the mimetype we want to import/export. If there's more than one plugin, the one * with the highest weight as defined in the json description will be taken * @param direction import or export * @return a pointer to the filter plugin or 0 if none could be found */ static KisImportExportFilter *filterForMimeType(const QString &mimetype, Direction direction); /** * Set the filter manager is batch mode (no dialog shown) * instead of the interactive mode (dialog shown) */ void setBatchMode(const bool batch); /** * Get if the filter manager is batch mode (true) * or in interactive mode (true) */ bool batchMode(void) const; void setUpdater(KoUpdaterPtr updater); static QString askForAudioFileName(const QString &defaultDir, QWidget *parent); private Q_SLOTS: private: struct ConversionResult; ConversionResult convert(Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync); void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration); bool askUserAboutExportConfiguration(QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, bool batchMode, const bool showWarnings, bool *alsoAsKra); KisImportExportFilter::ConversionStatus doImport(const QString &location, QSharedPointer filter); KisImportExportFilter::ConversionStatus doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra); KisImportExportFilter::ConversionStatus doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration); // Private API KisImportExportManager(const KisImportExportManager& rhs); KisImportExportManager &operator=(const KisImportExportManager& rhs); KisDocument *m_document; /// A static cache for the availability checks of filters static QStringList m_importMimeTypes; static QStringList m_exportMimeTypes; class Private; Private * const d; }; #endif // __KO_FILTER_MANAGER_H__ diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index 003ea429d3..2b0b8add6c 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1378 +1,1379 @@ /* * This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 1999 Carsten Pfeiffer * 2002 Patrick Julien * 2003-2011 Boudewijn Rempt * 2004 Clarence Dang * 2011 José Luis Vergara * 2017 L. E. Segovia * * 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 "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "input/kis_input_manager.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_canvas_controller.h" #include "canvas/kis_grid_manager.h" #include "dialogs/kis_dlg_blacklist_cleanup.h" #include "input/kis_input_profile_manager.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_canvas_controls_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_composite_progress_proxy.h" #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_control_frame.h" #include "kis_coordinates_converter.h" #include "KisDocument.h" #include "kis_favorite_resource_manager.h" #include "kis_filter_manager.h" #include "kis_group_layer.h" #include #include #include "kis_image_manager.h" #include #include "kis_mainwindow_observer.h" #include "kis_mask_manager.h" #include "kis_mimedata.h" #include "kis_mirror_manager.h" #include "kis_node_commands_adapter.h" #include "kis_node.h" #include "kis_node_manager.h" #include "kis_painting_assistants_manager.h" #include #include "kis_paintop_box.h" #include #include "KisPart.h" #include "KisPrintJob.h" #include #include "kis_resource_server_provider.h" #include "kis_selection.h" #include "kis_selection_manager.h" #include "kis_shape_controller.h" #include "kis_shape_layer.h" #include #include "kis_statusbar.h" #include #include #include "kis_tooltip_manager.h" #include #include "KisView.h" #include "kis_zoom_manager.h" #include "widgets/kis_floating_message.h" #include "kis_signal_auto_connection.h" #include "kis_script_manager.h" #include "kis_icon_utils.h" #include "kis_guides_manager.h" #include "kis_derived_resources.h" #include "dialogs/kis_delayed_save_dialog.h" #include class BlockingUserInputEventFilter : public QObject { bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if(dynamic_cast(event) || dynamic_cast(event) || dynamic_cast(event)) { return true; } else { return false; } } }; class KisViewManager::KisViewManagerPrivate { public: KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent) : filterManager(_q) , createTemplate(0) , saveIncremental(0) , saveIncrementalBackup(0) , openResourcesDirectory(0) , rotateCanvasRight(0) , rotateCanvasLeft(0) , resetCanvasRotation(0) , wrapAroundAction(0) , levelOfDetailAction(0) , showRulersAction(0) , rulersTrackMouseAction(0) , zoomTo100pct(0) , zoomIn(0) , zoomOut(0) , selectionManager(_q) , statusBar(_q) , controlFrame(_q, _q_parent) , nodeManager(_q) , imageManager(_q) , gridManager(_q) , canvasControlsManager(_q) , paintingAssistantsManager(_q) , actionManager(_q, _actionCollection) , mainWindow(0) , showFloatingMessage(true) , currentImageView(0) , canvasResourceProvider(_q) , canvasResourceManager() , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q) , actionCollection(_actionCollection) , mirrorManager(_q) , inputManager(_q) , scriptManager(_q) , actionAuthor(0) , showPixelGrid(0) { KisViewManager::initializeResourceManager(&canvasResourceManager); } public: KisFilterManager filterManager; KisAction *createTemplate; KisAction *createCopy; KisAction *saveIncremental; KisAction *saveIncrementalBackup; KisAction *openResourcesDirectory; KisAction *rotateCanvasRight; KisAction *rotateCanvasLeft; KisAction *resetCanvasRotation; KisAction *wrapAroundAction; KisAction *levelOfDetailAction; KisAction *showRulersAction; KisAction *rulersTrackMouseAction; KisAction *zoomTo100pct; KisAction *zoomIn; KisAction *zoomOut; KisAction *softProof; KisAction *gamutCheck; KisSelectionManager selectionManager; KisGuidesManager guidesManager; KisStatusBar statusBar; QPointer persistentImageProgressUpdater; QScopedPointer persistentUnthreadedProgressUpdaterRouter; QPointer persistentUnthreadedProgressUpdater; KisControlFrame controlFrame; KisNodeManager nodeManager; KisImageManager imageManager; KisGridManager gridManager; KisCanvasControlsManager canvasControlsManager; KisPaintingAssistantsManager paintingAssistantsManager; BlockingUserInputEventFilter blockingEventFilter; KisActionManager actionManager; QMainWindow* mainWindow; QPointer savedFloatingMessage; bool showFloatingMessage; QPointer currentImageView; KisCanvasResourceProvider canvasResourceProvider; KoCanvasResourceManager canvasResourceManager; KisSignalCompressor guiUpdateCompressor; KActionCollection *actionCollection; KisMirrorManager mirrorManager; KisInputManager inputManager; KisSignalAutoConnectionsStore viewConnections; KisScriptManager scriptManager; KSelectAction *actionAuthor; // Select action for author profile. KisAction *showPixelGrid; QByteArray canvasState; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QFlags windowFlags; #endif bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force); }; KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection) : d(new KisViewManagerPrivate(this, _actionCollection, parent)) { d->actionCollection = _actionCollection; d->mainWindow = dynamic_cast(parent); d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager); connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout())); createActions(); setupManagers(); // These initialization functions must wait until KisViewManager ctor is complete. d->statusBar.setup(); d->persistentImageProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentImageProgressUpdater->setRange(0,100); d->persistentImageProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentUnthreadedProgressUpdater->setRange(0,100); d->persistentUnthreadedProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdaterRouter.reset( new KoProgressUpdater(d->persistentUnthreadedProgressUpdater, KoProgressUpdater::Unthreaded)); d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true); d->controlFrame.setup(parent); //Check to draw scrollbars after "Canvas only mode" toggle is created. this->showHideScrollbars(); QScopedPointer dummy(new KoDummyCanvasController(actionCollection())); KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data()); QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility())); connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)), d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice))); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int))); connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), resourceProvider(), SLOT(slotNodeActivated(KisNodeSP))); connect(resourceProvider()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), d->controlFrame.paintopBox(), SLOT(slotCanvasResourceChanged(int,QVariant))); connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*))); connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions())); KisInputProfileManager::instance()->loadProfiles(); KisConfig cfg; d->showFloatingMessage = cfg.showCanvasMessages(); } KisViewManager::~KisViewManager() { KisConfig cfg; if (resourceProvider() && resourceProvider()->currentPreset()) { cfg.writeEntry("LastPreset", resourceProvider()->currentPreset()->name()); cfg.writeKoColor("LastForeGroundColor",resourceProvider()->fgColor()); cfg.writeKoColor("LastBackGroundColor",resourceProvider()->bgColor()); } cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength()); delete d; } void KisViewManager::initializeResourceManager(KoCanvasResourceManager *resourceManager) { resourceManager->addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisFlowResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisSizeResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter)); resourceManager->addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator)); } KActionCollection *KisViewManager::actionCollection() const { return d->actionCollection; } void KisViewManager::slotViewAdded(KisView *view) { // WARNING: this slot is called even when a view from another main windows is added! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.showAllStatusBarItems(); } } void KisViewManager::slotViewRemoved(KisView *view) { // WARNING: this slot is called even when a view from another main windows is removed! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.hideAllStatusBarItems(); } } void KisViewManager::setCurrentView(KisView *view) { bool first = true; if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(false); d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor)); first = false; KisDocument* doc = d->currentImageView->document(); if (doc) { doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater); doc->disconnect(this); } d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar); d->viewConnections.clear(); } QPointer imageView = qobject_cast(view); d->currentImageView = imageView; if (imageView) { d->softProof->setChecked(imageView->softProofing()); d->gamutCheck->setChecked(imageView->gamutCheck()); // Wait for the async image to have loaded KisDocument* doc = view->document(); // connect(canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), d->statusBar, SLOT(documentMousePositionChanged(QPointF))); // Restore the last used brush preset, color and background color. if (first) { KisConfig cfg; KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); QString lastPreset = cfg.readEntry("LastPreset", QString("Basic_tip_default")); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (!preset) { preset = rserver->resources().first(); } if (preset) { paintOpBox()->restoreResource(preset.data()); } const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor foreground(Qt::black, cs); d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground)); KoColor background(Qt::white, cs); d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background)); } KisCanvasController *canvasController = dynamic_cast(d->currentImageView->canvasController()); d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode())); d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15())); d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15())); d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation())); d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool))); d->wrapAroundAction->setChecked(canvasController->wrapAroundMode()); d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool))); d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode()); d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*))); d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool))); d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool))); d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100())); d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn())); d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut())); d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) ); d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) ); // set up progrress reporting doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater); d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation())); d->viewConnections.addUniqueConnection(d->showPixelGrid, SIGNAL(toggled(bool)), canvasController, SLOT(slotTogglePixelGrid(bool))); imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked()); imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked()); showHideScrollbars(); } d->filterManager.setView(imageView); d->selectionManager.setView(imageView); d->guidesManager.setView(imageView); d->nodeManager.setView(imageView); d->imageManager.setView(imageView); d->canvasControlsManager.setView(imageView); d->actionManager.setView(imageView); d->gridManager.setView(imageView); d->statusBar.setView(imageView); d->paintingAssistantsManager.setView(imageView); d->mirrorManager.setView(imageView); if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(true); d->currentImageView->canvasController()->activate(); d->currentImageView->canvasController()->setFocus(); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), resourceProvider(), SLOT(slotImageSizeChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigResolutionChanged(double,double)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(updateGUI())); d->viewConnections.addUniqueConnection( d->currentImageView->zoomManager()->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); } d->actionManager.updateGUI(); resourceProvider()->slotImageSizeChanged(); resourceProvider()->slotOnScreenResolutionChanged(); Q_EMIT viewChanged(); } KoZoomController *KisViewManager::zoomController() const { if (d->currentImageView) { return d->currentImageView->zoomController(); } return 0; } KisImageWSP KisViewManager::image() const { if (document()) { return document()->image(); } return 0; } KisCanvasResourceProvider * KisViewManager::resourceProvider() { return &d->canvasResourceProvider; } KisCanvas2 * KisViewManager::canvasBase() const { if (d && d->currentImageView) { return d->currentImageView->canvasBase(); } return 0; } QWidget* KisViewManager::canvas() const { if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) { return d->currentImageView->canvasBase()->canvasWidget(); } return 0; } KisStatusBar * KisViewManager::statusBar() const { return &d->statusBar; } void KisViewManager::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { d->statusBar.addStatusBarItem(widget, stretch, permanent); } void KisViewManager::removeStatusBarItem(QWidget *widget) { d->statusBar.removeStatusBarItem(widget); } KisPaintopBox* KisViewManager::paintOpBox() const { return d->controlFrame.paintopBox(); } QPointer KisViewManager::createUnthreadedUpdater(const QString &name) { return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false); } QPointer KisViewManager::createThreadedUpdater(const QString &name) { return d->statusBar.progressUpdater()->startSubtask(1, name, false); } KisSelectionManager * KisViewManager::selectionManager() { return &d->selectionManager; } KisNodeSP KisViewManager::activeNode() { return d->nodeManager.activeNode(); } KisLayerSP KisViewManager::activeLayer() { return d->nodeManager.activeLayer(); } KisPaintDeviceSP KisViewManager::activeDevice() { return d->nodeManager.activePaintDevice(); } KisZoomManager * KisViewManager::zoomManager() { if (d->currentImageView) { return d->currentImageView->zoomManager(); } return 0; } KisFilterManager * KisViewManager::filterManager() { return &d->filterManager; } KisImageManager * KisViewManager::imageManager() { return &d->imageManager; } KisInputManager* KisViewManager::inputManager() const { return &d->inputManager; } KisSelectionSP KisViewManager::selection() { if (d->currentImageView) { return d->currentImageView->selection(); } return 0; } bool KisViewManager::selectionEditable() { KisLayerSP layer = activeLayer(); if (layer) { KoProperties properties; QList masks = layer->childNodes(QStringList("KisSelectionMask"), properties); if (masks.size() == 1) { return masks[0]->isEditable(); } } // global selection is always editable return true; } KisUndoAdapter * KisViewManager::undoAdapter() { if (!document()) return 0; KisImageWSP image = document()->image(); Q_ASSERT(image); return image->undoAdapter(); } void KisViewManager::createActions() { KisConfig cfg; d->saveIncremental = actionManager()->createAction("save_incremental_version"); connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental())); d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup"); connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup())); connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved())); d->saveIncremental->setEnabled(false); d->saveIncrementalBackup->setEnabled(false); KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger"); connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger())); d->createTemplate = actionManager()->createAction("create_template"); connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate())); d->createCopy = actionManager()->createAction("create_copy"); connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy())); d->openResourcesDirectory = actionManager()->createAction("open_resources_directory"); connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory())); d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right"); d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left"); d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation"); d->wrapAroundAction = actionManager()->createAction("wrap_around_mode"); d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode"); d->softProof = actionManager()->createAction("softProof"); d->gamutCheck = actionManager()->createAction("gamutCheck"); KisAction *tAction = actionManager()->createAction("showStatusBar"); tAction->setChecked(cfg.showStatusBar()); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool))); tAction = actionManager()->createAction("view_show_canvas_only"); tAction->setChecked(false); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool))); //Workaround, by default has the same shortcut as mirrorCanvas KisAction *a = dynamic_cast(actionCollection()->action("format_italic")); if (a) { a->setDefaultShortcut(QKeySequence()); } a = actionManager()->createAction("edit_blacklist_cleanup"); connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup())); + actionManager()->createAction("ruler_pixel_multiple2"); d->showRulersAction = actionManager()->createAction("view_ruler"); d->showRulersAction->setChecked(cfg.showRulers()); connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool))); d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse"); d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse()); connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool))); d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct"); d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, ""); d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, ""); d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this); connect(d->actionAuthor, SIGNAL(triggered(const QString &)), this, SLOT(changeAuthorProfile(const QString &))); actionCollection()->addAction("settings_active_author", d->actionAuthor); slotUpdateAuthorProfileActions(); d->showPixelGrid = actionManager()->createAction("view_pixel_grid"); d->showPixelGrid->setChecked(cfg.pixelGridEnabled()); } void KisViewManager::setupManagers() { // Create the managers for filters, selections, layers etc. // XXX: When the currentlayer changes, call updateGUI on all // managers d->filterManager.setup(actionCollection(), actionManager()); d->selectionManager.setup(actionManager()); d->guidesManager.setup(actionManager()); d->nodeManager.setup(actionCollection(), actionManager()); d->imageManager.setup(actionManager()); d->gridManager.setup(actionManager()); d->paintingAssistantsManager.setup(actionManager()); d->canvasControlsManager.setup(actionManager()); d->mirrorManager.setup(actionCollection()); d->scriptManager.setup(actionCollection(), actionManager()); } void KisViewManager::updateGUI() { d->guiUpdateCompressor.start(); } void KisViewManager::slotBlacklistCleanup() { KisDlgBlacklistCleanup dialog; dialog.exec(); } KisNodeManager * KisViewManager::nodeManager() const { return &d->nodeManager; } KisActionManager* KisViewManager::actionManager() const { return &d->actionManager; } KisGridManager * KisViewManager::gridManager() const { return &d->gridManager; } KisGuidesManager * KisViewManager::guidesManager() const { return &d->guidesManager; } KisDocument *KisViewManager::document() const { if (d->currentImageView && d->currentImageView->document()) { return d->currentImageView->document(); } return 0; } KisScriptManager *KisViewManager::scriptManager() const { return &d->scriptManager; } int KisViewManager::viewCount() const { KisMainWindow *mw = qobject_cast(d->mainWindow); if (mw) { return mw->viewCount(); } return 0; } bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force) { const int busyWaitDelay = 1000; KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow); dialog.blockIfImageIsBusy(); return dialog.result() == QDialog::Accepted; } bool KisViewManager::blockUntilOperationsFinished(KisImageSP image) { return d->blockUntilOperationsFinishedImpl(image, false); } void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image) { d->blockUntilOperationsFinishedImpl(image, true); } void KisViewManager::slotCreateTemplate() { if (!document()) return; KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow()); } void KisViewManager::slotCreateCopy() { KisDocument *srcDoc = document(); if (!srcDoc) return; if (!this->blockUntilOperationsFinished(srcDoc->image())) return; KisDocument *doc = 0; { KisImageBarrierLocker l(srcDoc->image()); doc = srcDoc->clone(); } KIS_SAFE_ASSERT_RECOVER_RETURN(doc); QString name = srcDoc->documentInfo()->aboutInfo("name"); if (name.isEmpty()) { name = document()->url().toLocalFile(); } name = i18n("%1 (Copy)", name); doc->documentInfo()->setAboutInfo("title", name); KisPart::instance()->addDocument(doc); KisMainWindow *mw = qobject_cast(d->mainWindow); mw->addViewAndNotifyLoadingCompleted(doc); } QMainWindow* KisViewManager::qtMainWindow() const { if (d->mainWindow) return d->mainWindow; //Fallback for when we have not yet set the main window. QMainWindow* w = qobject_cast(qApp->activeWindow()); if(w) return w; return mainWindow(); } void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow) { d->mainWindow = newMainWindow; } void KisViewManager::slotDocumentSaved() { d->saveIncremental->setEnabled(true); d->saveIncrementalBackup->setEnabled(true); } void KisViewManager::slotSaveIncremental() { if (!document()) return; bool foundVersion; bool fileAlreadyExists; bool isBackup; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // Find current version filenames // v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well // Considering our incremental version and backup scheme, format is filename_001~001.ext QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); foundVersion = matches.at(0).isEmpty() ? false : true; // Ensure compatibility with Save Incremental Backup // If this regex is not kept separate, the entire algorithm needs modification; // It's simpler to just add this. QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regexAux.indexIn(fileName); // Perform the search QStringList matchesAux = regexAux.capturedTexts(); isBackup = matchesAux.at(0).isEmpty() ? false : true; // If the filename has a version, prepare it for incrementation if (foundVersion) { version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "_" } else { // TODO: this will not work with files extensions like jp2 // ...else, simply add a version to it so the next loop works QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(fileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(version); extensionPlusVersion.prepend("_"); fileName.replace(regex2, extensionPlusVersion); } // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("_"); if (!letter.isNull()) newVersion.append(letter); if (isBackup) { newVersion.append("~"); } else { newVersion.append("."); } fileName.replace(regex, newVersion); fileAlreadyExists = QFile(fileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } document()->setFileBatchMode(true); document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) { mainWindow()->updateCaption(); } } void KisViewManager::slotSaveIncrementalBackup() { if (!document()) return; bool workingOnBackup; bool fileAlreadyExists; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // First, discover if working on a backup file, or a normal file QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); workingOnBackup = matches.at(0).isEmpty() ? false : true; if (workingOnBackup) { // Try to save incremental version (of backup), use letter for alt versions version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "~" // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); QString backupFileName = document()->localFilePath(); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("~"); if (!letter.isNull()) newVersion.append(letter); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QFile::copy(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); if (mainWindow()) mainWindow()->updateCaption(); } else { // if NOT working on a backup... // Navigate directory searching for latest backup version, ignore letters const quint8 HARDCODED_DIGIT_COUNT = 3; QString baseNewVersion = "000"; QString backupFileName = document()->localFilePath(); QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(backupFileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(baseNewVersion); extensionPlusVersion.prepend("~"); backupFileName.replace(regex2, extensionPlusVersion); // Save version with 1 number higher than the highest version found ignoring letters do { newVersion = baseNewVersion; newVersion.prepend("~"); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { // Prepare the base for new version filename, increment by 1 int intVersion = baseNewVersion.toInt(0); ++intVersion; baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) { baseNewVersion.prepend("0"); } } } while (fileAlreadyExists); // Save both as backup and on current file for interapplication workflow document()->setFileBatchMode(true); QFile::copy(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) mainWindow()->updateCaption(); } } void KisViewManager::disableControls() { // prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel // this is for Bug 250944 // the solution blocks all wheel, mouse and key event, while dragging with the freehand tool // see KisToolFreehand::initPaint() and endPaint() d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->installEventFilter(&d->blockingEventFilter); } } void KisViewManager::enableControls() { d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->removeEventFilter(&d->blockingEventFilter); } } void KisViewManager::showStatusBar(bool toggled) { KisMainWindow *mw = mainWindow(); if(mw && mw->statusBar()) { mw->statusBar()->setVisible(toggled); KisConfig cfg; cfg.setShowStatusBar(toggled); } } void KisViewManager::switchCanvasOnly(bool toggled) { KisConfig cfg; KisMainWindow* main = mainWindow(); if(!main) { dbgUI << "Unable to switch to canvas-only mode, main window not found"; return; } if (toggled) { d->canvasState = qtMainWindow()->saveState(); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) d->windowFlags = main->windowState(); #endif } if (cfg.hideStatusbarFullscreen()) { if (main->statusBar()) { if (!toggled) { if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->statusBar()->property("wasvisible").toBool()) { main->statusBar()->setVisible(true); } } } else { main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible()); main->statusBar()->setVisible(false); } } } if (cfg.hideDockersFullscreen()) { KisAction* action = qobject_cast(main->actionCollection()->action("view_toggledockers")); if (action) { action->setCheckable(true); if (toggled) { if (action->isChecked()) { cfg.setShowDockers(action->isChecked()); action->setChecked(false); } else { cfg.setShowDockers(false); } } else { action->setChecked(cfg.showDockers()); } } } // QT in windows does not return to maximized upon 4th tab in a row // https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/ if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) { if(toggled) { main->setWindowState( main->windowState() | Qt::WindowFullScreen); } else { main->setWindowState( main->windowState() & ~Qt::WindowFullScreen); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // If window was maximized prior to fullscreen, restore that if (d->windowFlags & Qt::WindowMaximized) { main->setWindowState( main->windowState() | Qt::WindowMaximized); } #endif } } if (cfg.hideMenuFullscreen()) { if (!toggled) { if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->menuBar()->property("wasvisible").toBool()) { main->menuBar()->setVisible(true); } } } else { main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible()); main->menuBar()->setVisible(false); } } if (cfg.hideToolbarFullscreen()) { QList toolBars = main->findChildren(); Q_FOREACH (QToolBar* toolbar, toolBars) { if (!toggled) { if (toolbar->dynamicPropertyNames().contains("wasvisible")) { if (toolbar->property("wasvisible").toBool()) { toolbar->setVisible(true); } } } else { toolbar->setProperty("wasvisible", toolbar->isVisible()); toolbar->setVisible(false); } } } showHideScrollbars(); if (toggled) { // show a fading heads-up display about the shortcut to go back showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.", actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon()); } else { main->restoreState(d->canvasState); } } void KisViewManager::toggleTabletLogger() { d->inputManager.toggleTabletLogger(); } void KisViewManager::openResourcesDirectory() { QString dir = KoResourcePaths::locateLocal("data", ""); QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); } void KisViewManager::updateIcons() { if (mainWindow()) { QList dockers = mainWindow()->dockWidgets(); Q_FOREACH (QDockWidget* dock, dockers) { dbgKrita << "name " << dock->objectName(); KoDockWidgetTitleBar* titlebar = dynamic_cast(dock->titleBarWidget()); if (titlebar) { titlebar->updateIcons(); } QObjectList objects; objects.append(dock); while (!objects.isEmpty()) { QObject* object = objects.takeFirst(); objects.append(object->children()); KisIconUtils::updateIconCommon(object); } } } } void KisViewManager::initializeStatusBarVisibility() { KisConfig cfg; d->mainWindow->statusBar()->setVisible(cfg.showStatusBar()); } void KisViewManager::guiUpdateTimeout() { d->nodeManager.updateGUI(); d->selectionManager.updateGUI(); d->filterManager.updateGUI(); if (zoomManager()) { zoomManager()->updateGUI(); } d->gridManager.updateGUI(); d->actionManager.updateGUI(); } void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->currentImageView) return; d->currentImageView->showFloatingMessageImpl(message, icon, timeout, priority, alignment); emit floatingMessageRequested(message, icon.name()); } KisMainWindow *KisViewManager::mainWindow() const { return qobject_cast(d->mainWindow); } void KisViewManager::showHideScrollbars() { if (!d->currentImageView) return; if (!d->currentImageView->canvasController()) return; KisConfig cfg; bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked(); if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } void KisViewManager::slotSaveShowRulersState(bool value) { KisConfig cfg; cfg.setShowRulers(value); } void KisViewManager::slotSaveRulersTrackMouseState(bool value) { KisConfig cfg; cfg.setRulersTrackMouse(value); } void KisViewManager::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisViewManager::changeAuthorProfile(const QString &profileName) { KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author"); if (profileName.isEmpty() || profileName == i18nc("choice for author profile", "Anonymous")) { appAuthorGroup.writeEntry("active-profile", ""); } else { appAuthorGroup.writeEntry("active-profile", profileName); } appAuthorGroup.sync(); Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { doc->documentInfo()->updateParameters(); } } void KisViewManager::slotUpdateAuthorProfileActions() { Q_ASSERT(d->actionAuthor); if (!d->actionAuthor) { return; } d->actionAuthor->clear(); d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous")); KConfigGroup authorGroup(KoGlobal::calligraConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/"; QStringList filters = QStringList() << "*.authorinfo"; QDir dir(authorInfo); Q_FOREACH(QString entry, dir.entryList(filters)) { int ln = QString(".authorinfo").size(); entry.chop(ln); if (!profiles.contains(entry)) { profiles.append(entry); } } Q_FOREACH (const QString &profile , profiles) { d->actionAuthor->addAction(profile); } KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); if (profileName == "anonymous" || profileName.isEmpty()) { d->actionAuthor->setCurrentItem(0); } else if (profiles.contains(profileName)) { d->actionAuthor->setCurrentAction(profileName); } } diff --git a/libs/ui/KisViewManager.h b/libs/ui/KisViewManager.h index 20b46b512e..b712e9b1b7 100644 --- a/libs/ui/KisViewManager.h +++ b/libs/ui/KisViewManager.h @@ -1,272 +1,273 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_GUI_CLIENT_H #define KIS_GUI_CLIENT_H #include #include #include #include #include #include #include #include "kis_floating_message.h" class QPoint; class KisView; class KisCanvas2; class KisCanvasResourceProvider; class KisDocument; class KisFilterManager; class KisGridManager; class KisGuidesManager; class KisImageManager; class KisNodeManager; class KisPaintingAssistantsManager; class KisPaintopBox; class KisSelectionManager; class KisStatusBar; class KisUndoAdapter; class KisZoomManager; class KisPaintopBox; class KisActionManager; class KisScriptManager; class KisInputManager; class KoUpdater; class KoProgressUpdater; /** * KisViewManager manages the collection of views shown in a single mainwindow. */ class KRITAUI_EXPORT KisViewManager : public QObject { Q_OBJECT public: /** * Construct a new view on the krita document. * @param document the document we show. * @param parent a parent widget we show ourselves in. */ KisViewManager(QWidget *parent, KActionCollection *actionCollection); ~KisViewManager() override; /** * Retrieves the entire action collection. */ virtual KActionCollection* actionCollection() const; public: // Krita specific interfaces void setCurrentView(KisView *view); /// Return the image this view is displaying KisImageWSP image() const; KoZoomController *zoomController() const; /// The resource provider contains all per-view settings, such as /// current color, current paint op etc. KisCanvasResourceProvider *resourceProvider(); /// Return the canvasbase class KisCanvas2 *canvasBase() const; /// Return the actual widget that is displaying the current image QWidget* canvas() const; /// Return the wrapper class around the statusbar KisStatusBar *statusBar() const; /** * This adds a widget to the statusbar for this view. * If you use this method instead of using statusBar() directly, * KisView will take care of removing the items when the view GUI is deactivated * and readding them when it is reactivated. * The parameters are the same as QStatusBar::addWidget(). - */ + */ void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false); + /** * Remove a widget from the statusbar for this view. */ void removeStatusBarItem(QWidget * widget); KisPaintopBox* paintOpBox() const; /// create a new progress updater QPointer createUnthreadedUpdater(const QString &name); QPointer createThreadedUpdater(const QString &name); /// The selection manager handles everything action related to /// selections. KisSelectionManager *selectionManager(); /// The node manager handles everything about nodes KisNodeManager *nodeManager() const; KisActionManager *actionManager() const; /** * Convenience method to get at the active node, which may be * a layer or a mask or a selection */ KisNodeSP activeNode(); /// Convenience method to get at the active layer KisLayerSP activeLayer(); /// Convenience method to get at the active paint device KisPaintDeviceSP activeDevice(); /// The filtermanager handles everything action-related to filters KisFilterManager *filterManager(); /// The image manager handles everything action-related to the /// current image KisImageManager *imageManager(); /// Filters events and sends them to canvas actions KisInputManager *inputManager() const; /// Convenience method to get at the active selection (the /// selection of the current layer, or, if that does not exist, /// the global selection. KisSelectionSP selection(); /// Checks if the current global or local selection is editable bool selectionEditable(); /// The undo adapter is used to add commands to the undo stack KisUndoAdapter *undoAdapter(); KisDocument *document() const; KisScriptManager *scriptManager() const; int viewCount() const; /** * @brief blockUntilOperationsFinished blocks the GUI of the application until execution * of actions on \p image is finished * @param image the image which we should wait for * @return true if the image has finished execution of the actions, false if * the user cancelled operation */ bool blockUntilOperationsFinished(KisImageSP image); /** * @brief blockUntilOperationsFinished blocks the GUI of the application until execution * of actions on \p image is finished. Does *not* provide a "Cancel" button. So the * user is forced to wait. * @param image the image which we should wait for */ void blockUntilOperationsFinishedForced(KisImageSP image); public: KisGridManager * gridManager() const; KisGuidesManager * guidesManager() const; /// disable and enable toolbar controls. used for disabling them during painting. void enableControls(); void disableControls(); /// shows a floating message in the top right corner of the canvas void showFloatingMessage(const QString &message, const QIcon& icon, int timeout = 4500, KisFloatingMessage::Priority priority = KisFloatingMessage::Medium, int alignment = Qt::AlignCenter | Qt::TextWordWrap); /// @return the KoMaindow this view is in, or 0 KisMainWindow *mainWindow() const; /// The QMainWindow associated with this view. This is most likely going to be shell(), but /// when running as Gemini or Sketch, this will be set to the applications' own QMainWindow. /// This can be checked by qobject_casting to KisMainWindow to check the difference. QMainWindow* qtMainWindow() const; /// The mainWindow function will return the shell() value, unless this function is called /// with a non-null value. To make it return shell() again, simply pass null to this function. void setQtMainWindow(QMainWindow* newMainWindow); static void initializeResourceManager(KoCanvasResourceManager *resourceManager); public Q_SLOTS: void switchCanvasOnly(bool toggled); void setShowFloatingMessage(bool show); void showHideScrollbars(); /// Visit all managers to update gui elements, e.g. enable / disable actions. /// This is heavy-duty call, so it uses a compressor. void updateGUI(); /// Update the style of all the icons void updateIcons(); void slotViewAdded(KisView *view); void slotViewRemoved(KisView *view); Q_SIGNALS: void floatingMessageRequested(const QString &message, const QString &iconName); /** * @brief viewChanged * sent out when the view has changed. */ void viewChanged(); private Q_SLOTS: void slotBlacklistCleanup(); void slotCreateTemplate(); void slotCreateCopy(); void slotDocumentSaved(); void slotSaveIncremental(); void slotSaveIncrementalBackup(); void showStatusBar(bool toggled); void toggleTabletLogger(); void openResourcesDirectory(); void initializeStatusBarVisibility(); void guiUpdateTimeout(); void changeAuthorProfile(const QString &profileName); void slotUpdateAuthorProfileActions(); void slotSaveShowRulersState(bool value); void slotSaveRulersTrackMouseState(bool value); private: void createActions(); void setupManagers(); /// The zoommanager handles everything action-related to zooming KisZoomManager * zoomManager(); private: class KisViewManagerPrivate; KisViewManagerPrivate * const d; }; #endif diff --git a/libs/ui/canvas/kis_guides_config.cpp b/libs/ui/canvas/kis_guides_config.cpp index c88df8f739..4c492470e2 100644 --- a/libs/ui/canvas/kis_guides_config.cpp +++ b/libs/ui/canvas/kis_guides_config.cpp @@ -1,246 +1,296 @@ /* This file is part of the KDE project Copyright (C) 2006 Laurent Montel Copyright (C) 2008 Jan Hambrecht Copyright (c) 2015 Dmitry Kazakov 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 "kis_guides_config.h" #include #include #include #include "kis_config.h" #include "kis_dom_utils.h" class Q_DECL_HIDDEN KisGuidesConfig::Private { public: Private() : showGuides(false) , snapToGuides(false) , lockGuides(false) + , rulersMultiple2(false) + , unitType(KoUnit::Pixel) {} bool operator==(const Private &rhs) { return horzGuideLines == rhs.horzGuideLines && vertGuideLines == rhs.vertGuideLines && showGuides == rhs.showGuides && snapToGuides == rhs.snapToGuides && lockGuides == rhs.lockGuides && guidesColor == rhs.guidesColor && - guidesLineType == rhs.guidesLineType; + guidesLineType == rhs.guidesLineType && + rulersMultiple2 == rhs.rulersMultiple2 && + unitType == rhs.unitType; } QList horzGuideLines; QList vertGuideLines; bool showGuides; bool snapToGuides; bool lockGuides; + bool rulersMultiple2; + + KoUnit::Type unitType; QColor guidesColor; LineTypeInternal guidesLineType; Qt::PenStyle toPenStyle(LineTypeInternal type); }; KisGuidesConfig::KisGuidesConfig() : d(new Private()) { loadStaticData(); } KisGuidesConfig::~KisGuidesConfig() { } KisGuidesConfig::KisGuidesConfig(const KisGuidesConfig &rhs) : d(new Private(*rhs.d)) { } KisGuidesConfig& KisGuidesConfig::operator=(const KisGuidesConfig &rhs) { if (&rhs != this) { *d = *rhs.d; } return *this; } bool KisGuidesConfig::operator==(const KisGuidesConfig &rhs) const { return *d == *rhs.d; } void KisGuidesConfig::setHorizontalGuideLines(const QList &lines) { d->horzGuideLines = lines; } void KisGuidesConfig::setVerticalGuideLines(const QList &lines) { d->vertGuideLines = lines; } void KisGuidesConfig::addGuideLine(Qt::Orientation o, qreal pos) { if (o == Qt::Horizontal) { d->horzGuideLines.append(pos); } else { d->vertGuideLines.append(pos); } } bool KisGuidesConfig::showGuideLines() const { return d->showGuides; } void KisGuidesConfig::setShowGuideLines(bool show) { d->showGuides = show; } bool KisGuidesConfig::showGuides() const { return d->showGuides; } void KisGuidesConfig::setShowGuides(bool value) { d->showGuides = value; } bool KisGuidesConfig::lockGuides() const { return d->lockGuides; } void KisGuidesConfig::setLockGuides(bool value) { d->lockGuides = value; } bool KisGuidesConfig::snapToGuides() const { return d->snapToGuides; } void KisGuidesConfig::setSnapToGuides(bool value) { d->snapToGuides = value; } +bool KisGuidesConfig::rulersMultiple2() const +{ + return d->rulersMultiple2; +} + +void KisGuidesConfig::setRulersMultiple2(bool value) +{ + d->rulersMultiple2 = value; +} + +KoUnit::Type KisGuidesConfig::unitType() const +{ + return d->unitType; +} + +void KisGuidesConfig::setUnitType(const KoUnit::Type type) +{ + d->unitType = type; +} + KisGuidesConfig::LineTypeInternal KisGuidesConfig::guidesLineType() const { return d->guidesLineType; } void KisGuidesConfig::setGuidesLineType(LineTypeInternal value) { d->guidesLineType = value; } QColor KisGuidesConfig::guidesColor() const { return d->guidesColor; } void KisGuidesConfig::setGuidesColor(const QColor &value) { d->guidesColor = value; } Qt::PenStyle KisGuidesConfig::Private::toPenStyle(LineTypeInternal type) { return type == LINE_SOLID ? Qt::SolidLine : type == LINE_DASHED ? Qt::DashLine : type == LINE_DOTTED ? Qt::DotLine : Qt::DashDotDotLine; } QPen KisGuidesConfig::guidesPen() const { return QPen(d->guidesColor, 0, d->toPenStyle(d->guidesLineType)); } const QList& KisGuidesConfig::horizontalGuideLines() const { return d->horzGuideLines; } const QList& KisGuidesConfig::verticalGuideLines() const { return d->vertGuideLines; } bool KisGuidesConfig::hasGuides() const { return !d->horzGuideLines.isEmpty() || !d->vertGuideLines.isEmpty(); } void KisGuidesConfig::loadStaticData() { KisConfig cfg; d->guidesLineType = LineTypeInternal(cfg.guidesLineStyle()); d->guidesColor = cfg.guidesColor(); } void KisGuidesConfig::saveStaticData() const { KisConfig cfg; cfg.setGuidesLineStyle(d->guidesLineType); cfg.setGuidesColor(d->guidesColor); } QDomElement KisGuidesConfig::saveToXml(QDomDocument& doc, const QString &tag) const { QDomElement guidesElement = doc.createElement(tag); KisDomUtils::saveValue(&guidesElement, "showGuides", d->showGuides); KisDomUtils::saveValue(&guidesElement, "snapToGuides", d->snapToGuides); KisDomUtils::saveValue(&guidesElement, "lockGuides", d->lockGuides); KisDomUtils::saveValue(&guidesElement, "horizontalGuides", d->horzGuideLines.toVector()); KisDomUtils::saveValue(&guidesElement, "verticalGuides", d->vertGuideLines.toVector()); + KisDomUtils::saveValue(&guidesElement, "rulersMultiple2", d->rulersMultiple2); + KoUnit tmp(d->unitType); + KisDomUtils::saveValue(&guidesElement, "unit", tmp.symbol()); + return guidesElement; } bool KisGuidesConfig::loadFromXml(const QDomElement &parent) { bool result = true; result &= KisDomUtils::loadValue(parent, "showGuides", &d->showGuides); result &= KisDomUtils::loadValue(parent, "snapToGuides", &d->snapToGuides); result &= KisDomUtils::loadValue(parent, "lockGuides", &d->lockGuides); QVector hGuides; QVector vGuides; result &= KisDomUtils::loadValue(parent, "horizontalGuides", &hGuides); result &= KisDomUtils::loadValue(parent, "verticalGuides", &vGuides); d->horzGuideLines = QList::fromVector(hGuides); d->vertGuideLines = QList::fromVector(vGuides); + result &= KisDomUtils::loadValue(parent, "rulersMultiple2", &d->rulersMultiple2); + QString unit; + result &= KisDomUtils::loadValue(parent, "unit", &unit); + bool ok = false; + KoUnit tmp = KoUnit::fromSymbol(unit, &ok); + if (ok) { + d->unitType = tmp.type(); + } + result &= ok; + + return result; } + +bool KisGuidesConfig::isDefault() const +{ + KisGuidesConfig defaultObject; + defaultObject.loadStaticData(); + + return *this == defaultObject; +} diff --git a/libs/ui/canvas/kis_guides_config.h b/libs/ui/canvas/kis_guides_config.h index 4b5397dfbe..ed97a51333 100644 --- a/libs/ui/canvas/kis_guides_config.h +++ b/libs/ui/canvas/kis_guides_config.h @@ -1,121 +1,130 @@ /* This file is part of the KDE project Copyright (C) 2006 Laurent Montel Copyright (C) 2008 Jan Hambrecht Copyright (c) 2015 Dmitry Kazakov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOGUIDESDATA_H #define KOGUIDESDATA_H #include "kritaui_export.h" #include #include #include +#include class QDomElement; class QDomDocument; class QColor; class QPen; class KRITAUI_EXPORT KisGuidesConfig : boost::equality_comparable { public: enum LineTypeInternal { LINE_SOLID = 0, LINE_DASHED, LINE_DOTTED }; public: KisGuidesConfig(); ~KisGuidesConfig(); KisGuidesConfig(const KisGuidesConfig &rhs); KisGuidesConfig& operator=(const KisGuidesConfig &rhs); bool operator==(const KisGuidesConfig &rhs) const; /** * @brief Set the positions of the horizontal guide lines * * @param lines a list of positions of the horizontal guide lines */ void setHorizontalGuideLines(const QList &lines); /** * @brief Set the positions of the vertical guide lines * * @param lines a list of positions of the vertical guide lines */ void setVerticalGuideLines(const QList &lines); /** * @brief Add a guide line to the canvas. * * @param orientation the orientation of the guide line * @param position the position in document coordinates of the guide line */ void addGuideLine(Qt::Orientation orientation, qreal position); /** * @brief Display or not guide lines */ bool showGuideLines() const; /** * @param show display or not guide line */ void setShowGuideLines(bool show); bool showGuides() const; void setShowGuides(bool value); bool lockGuides() const; void setLockGuides(bool value); bool snapToGuides() const; void setSnapToGuides(bool value); + bool rulersMultiple2() const; + void setRulersMultiple2(bool value); + + KoUnit::Type unitType() const; + void setUnitType(KoUnit::Type type); + LineTypeInternal guidesLineType() const; void setGuidesLineType(LineTypeInternal value); QColor guidesColor() const; void setGuidesColor(const QColor &value); QPen guidesPen() const; /// Returns the list of horizontal guide lines. const QList& horizontalGuideLines() const; /// Returns the list of vertical guide lines. const QList& verticalGuideLines() const; bool hasGuides() const; void loadStaticData(); void saveStaticData() const; QDomElement saveToXml(QDomDocument& doc, const QString &tag) const; bool loadFromXml(const QDomElement &parent); + bool isDefault() const; + private: class Private; const QScopedPointer d; }; #endif diff --git a/libs/ui/canvas/kis_guides_manager.cpp b/libs/ui/canvas/kis_guides_manager.cpp index 2ebf8150ac..1d1d04e72b 100644 --- a/libs/ui/canvas/kis_guides_manager.cpp +++ b/libs/ui/canvas/kis_guides_manager.cpp @@ -1,765 +1,792 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_guides_manager.h" #include #include #include "kis_guides_decoration.h" #include #include "kis_guides_config.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_signals_blocker.h" #include "input/kis_input_manager.h" #include "kis_coordinates_converter.h" #include "kis_zoom_manager.h" #include "kis_signal_auto_connection.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_algebra_2d.h" #include #include "kis_snap_line_strategy.h" #include "kis_change_guides_command.h" #include "kis_snap_config.h" #include "kis_coordinates_converter.h" #include "kis_canvas2.h" #include "kis_signal_compressor.h" struct KisGuidesManager::Private { Private(KisGuidesManager *_q) : q(_q), decoration(0), invalidGuide(Qt::Horizontal, -1), currentGuide(invalidGuide), cursorSwitched(false), dragStartGuidePos(0), updateDocumentCompressor(40, KisSignalCompressor::FIRST_ACTIVE), shouldSetModified(false) {} KisGuidesManager *q; KisGuidesDecoration *decoration; KisGuidesConfig guidesConfig; KisSnapConfig snapConfig; QPointer view; typedef QPair GuideHandle; GuideHandle findGuide(const QPointF &docPos); bool isGuideValid(const GuideHandle &h); qreal guideValue(const GuideHandle &h); void setGuideValue(const GuideHandle &h, qreal value); void deleteGuide(const GuideHandle &h); const GuideHandle invalidGuide; bool updateCursor(const QPointF &docPos); void initDragStart(const GuideHandle &guide, const QPointF &dragStart, qreal guideValue, bool snapToStart); bool mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers); bool mouseReleaseHandler(const QPointF &docPos); void updateSnappingStatus(const KisGuidesConfig &value); QPointF alignToPixels(const QPointF docPoint); QPointF getDocPointFromEvent(QEvent *event); Qt::MouseButton getButtonFromEvent(QEvent *event); QAction* createShortenedAction(const QString &text, const QString &parentId, QObject *parent); void syncAction(const QString &actionName, bool value); GuideHandle currentGuide; bool cursorSwitched; QCursor oldCursor; QPointF dragStartDoc; QPointF dragPointerOffset; qreal dragStartGuidePos; KisSignalAutoConnectionsStore viewConnections; KisSignalCompressor updateDocumentCompressor; bool shouldSetModified; }; KisGuidesManager::KisGuidesManager(QObject *parent) : QObject(parent), m_d(new Private(this)) { connect(&m_d->updateDocumentCompressor, SIGNAL(timeout()), SLOT(slotUploadConfigToDocument())); } KisGuidesManager::~KisGuidesManager() { } void KisGuidesManager::setGuidesConfig(const KisGuidesConfig &config) { if (config == m_d->guidesConfig) return; setGuidesConfigImpl(config, true); } void KisGuidesManager::slotDocumentRequestedConfig(const KisGuidesConfig &config) { if (config == m_d->guidesConfig) return; setGuidesConfigImpl(config, false); } void KisGuidesManager::slotUploadConfigToDocument() { const KisGuidesConfig &value = m_d->guidesConfig; KisDocument *doc = m_d->view ? m_d->view->document() : 0; if (doc) { KisSignalsBlocker b(doc); if (m_d->shouldSetModified) { KUndo2Command *cmd = new KisChangeGuidesCommand(doc, value); doc->addCommand(cmd); } else { doc->setGuidesConfig(value); } value.saveStaticData(); } m_d->shouldSetModified = false; } void KisGuidesManager::setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified) { m_d->guidesConfig = value; if (m_d->decoration && value != m_d->decoration->guidesConfig()) { m_d->decoration->setVisible(value.showGuides()); m_d->decoration->setGuidesConfig(value); } m_d->shouldSetModified |= emitModified; m_d->updateDocumentCompressor.start(); const bool shouldFilterEvent = value.showGuides() && !value.lockGuides() && value.hasGuides(); attachEventFilterImpl(shouldFilterEvent); syncActionsStatus(); if (!m_d->isGuideValid(m_d->currentGuide)) { m_d->updateSnappingStatus(value); } + if (m_d->view) { + m_d->view->document()->setUnit(KoUnit(m_d->guidesConfig.unitType())); + m_d->view->viewManager()->actionManager()->actionByName("ruler_pixel_multiple2")->setChecked(value.rulersMultiple2()); + } + emit sigRequestUpdateGuidesConfig(m_d->guidesConfig); } void KisGuidesManager::attachEventFilterImpl(bool value) { if (!m_d->view) return; KisInputManager *inputManager = m_d->view->globalInputManager(); if (inputManager) { if (value) { inputManager->attachPriorityEventFilter(this, 100); } else { inputManager->detachPriorityEventFilter(this); } } } void KisGuidesManager::Private::syncAction(const QString &actionName, bool value) { KisActionManager *actionManager = view->viewManager()->actionManager(); KisAction *action = actionManager->actionByName(actionName); KIS_ASSERT_RECOVER_RETURN(action); KisSignalsBlocker b(action); action->setChecked(value); } void KisGuidesManager::syncActionsStatus() { if (!m_d->view) return; m_d->syncAction("view_show_guides", m_d->guidesConfig.showGuides()); m_d->syncAction("view_lock_guides", m_d->guidesConfig.lockGuides()); m_d->syncAction("view_snap_to_guides", m_d->guidesConfig.snapToGuides()); m_d->syncAction("view_snap_orthogonal", m_d->snapConfig.orthogonal()); m_d->syncAction("view_snap_node", m_d->snapConfig.node()); m_d->syncAction("view_snap_extension", m_d->snapConfig.extension()); m_d->syncAction("view_snap_intersection", m_d->snapConfig.intersection()); m_d->syncAction("view_snap_bounding_box", m_d->snapConfig.boundingBox()); m_d->syncAction("view_snap_image_bounds", m_d->snapConfig.imageBounds()); m_d->syncAction("view_snap_image_center", m_d->snapConfig.imageCenter()); } void KisGuidesManager::Private::updateSnappingStatus(const KisGuidesConfig &value) { if (!view) return; KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); KisSnapLineStrategy *guidesSnap = 0; if (value.snapToGuides()) { guidesSnap = new KisSnapLineStrategy(KoSnapGuide::GuideLineSnapping); guidesSnap->setHorizontalLines(value.horizontalGuideLines()); guidesSnap->setVerticalLines(value.verticalGuideLines()); } snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap); snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap); snapGuide->enableSnapStrategy(KoSnapGuide::OrthogonalSnapping, snapConfig.orthogonal()); snapGuide->enableSnapStrategy(KoSnapGuide::NodeSnapping, snapConfig.node()); snapGuide->enableSnapStrategy(KoSnapGuide::ExtensionSnapping, snapConfig.extension()); snapGuide->enableSnapStrategy(KoSnapGuide::IntersectionSnapping, snapConfig.intersection()); snapGuide->enableSnapStrategy(KoSnapGuide::BoundingBoxSnapping, snapConfig.boundingBox()); snapGuide->enableSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, snapConfig.imageBounds()); snapGuide->enableSnapStrategy(KoSnapGuide::DocumentCenterSnapping, snapConfig.imageCenter()); snapConfig.saveStaticData(); } bool KisGuidesManager::showGuides() const { return m_d->guidesConfig.showGuides(); } void KisGuidesManager::setShowGuides(bool value) { m_d->guidesConfig.setShowGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } bool KisGuidesManager::lockGuides() const { return m_d->guidesConfig.lockGuides(); } void KisGuidesManager::setLockGuides(bool value) { m_d->guidesConfig.setLockGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } bool KisGuidesManager::snapToGuides() const { return m_d->guidesConfig.snapToGuides(); } void KisGuidesManager::setSnapToGuides(bool value) { m_d->guidesConfig.setSnapToGuides(value); setGuidesConfigImpl(m_d->guidesConfig); } +bool KisGuidesManager::rulersMultiple2() const +{ + return m_d->guidesConfig.rulersMultiple2(); +} + +void KisGuidesManager::setRulersMultiple2(bool value) +{ + m_d->guidesConfig.setRulersMultiple2(value); + setGuidesConfigImpl(m_d->guidesConfig); +} + +KoUnit::Type KisGuidesManager::unitType() const +{ + return m_d->guidesConfig.unitType(); +} + +void KisGuidesManager::setUnitType(const KoUnit::Type type) +{ + m_d->guidesConfig.setUnitType(type); + setGuidesConfigImpl(m_d->guidesConfig); +} + void KisGuidesManager::setup(KisActionManager *actionManager) { KisAction *action = 0; action = actionManager->createAction("view_show_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setShowGuides(bool))); action = actionManager->createAction("view_lock_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setLockGuides(bool))); action = actionManager->createAction("view_snap_to_guides"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToGuides(bool))); action = actionManager->createAction("show_snap_options_popup"); connect(action, SIGNAL(triggered()), this, SLOT(slotShowSnapOptions())); action = actionManager->createAction("view_snap_orthogonal"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapOrthogonal(bool))); action = actionManager->createAction("view_snap_node"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapNode(bool))); action = actionManager->createAction("view_snap_extension"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapExtension(bool))); action = actionManager->createAction("view_snap_intersection"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapIntersection(bool))); action = actionManager->createAction("view_snap_bounding_box"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapBoundingBox(bool))); action = actionManager->createAction("view_snap_image_bounds"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageBounds(bool))); action = actionManager->createAction("view_snap_image_center"); connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageCenter(bool))); m_d->updateSnappingStatus(m_d->guidesConfig); syncActionsStatus(); } void KisGuidesManager::setView(QPointer view) { if (m_d->view) { KoSnapGuide *snapGuide = m_d->view->canvasBase()->snapGuide(); snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, 0); snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, false); if (m_d->updateDocumentCompressor.isActive()) { m_d->updateDocumentCompressor.stop(); slotUploadConfigToDocument(); } m_d->decoration = 0; m_d->viewConnections.clear(); attachEventFilterImpl(false); } m_d->view = view; if (m_d->view) { KisGuidesDecoration* decoration = qobject_cast(m_d->view->canvasBase()->decoration(GUIDES_DECORATION_ID).data()); if (!decoration) { decoration = new KisGuidesDecoration(m_d->view); m_d->view->canvasBase()->addDecoration(decoration); } m_d->decoration = decoration; m_d->guidesConfig = m_d->view->document()->guidesConfig(); setGuidesConfigImpl(m_d->guidesConfig, false); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation, const QPoint&)), this, SLOT(slotGuideCreationInProgress(Qt::Orientation, const QPoint&))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation, const QPoint&)), this, SLOT(slotGuideCreationFinished(Qt::Orientation, const QPoint&))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation, const QPoint&)), this, SLOT(slotGuideCreationInProgress(Qt::Orientation, const QPoint&))); m_d->viewConnections.addUniqueConnection( m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation, const QPoint&)), this, SLOT(slotGuideCreationFinished(Qt::Orientation, const QPoint&))); m_d->viewConnections.addUniqueConnection( m_d->view->document(), SIGNAL(sigGuidesConfigChanged(const KisGuidesConfig &)), this, SLOT(slotDocumentRequestedConfig(const KisGuidesConfig &))); } } KisGuidesManager::Private::GuideHandle KisGuidesManager::Private::findGuide(const QPointF &docPos) { const int snapRadius = 16; GuideHandle nearestGuide = invalidGuide; qreal nearestRadius = std::numeric_limits::max(); for (int i = 0; i < guidesConfig.horizontalGuideLines().size(); i++) { const qreal guide = guidesConfig.horizontalGuideLines()[i]; const qreal radius = qAbs(docPos.y() - guide); if (radius < snapRadius && radius < nearestRadius) { nearestGuide = GuideHandle(Qt::Horizontal, i); nearestRadius = radius; } } for (int i = 0; i < guidesConfig.verticalGuideLines().size(); i++) { const qreal guide = guidesConfig.verticalGuideLines()[i]; const qreal radius = qAbs(docPos.x() - guide); if (radius < snapRadius && radius < nearestRadius) { nearestGuide = GuideHandle(Qt::Vertical, i); nearestRadius = radius; } } return nearestGuide; } bool KisGuidesManager::Private::isGuideValid(const GuideHandle &h) { return h.second >= 0; } qreal KisGuidesManager::Private::guideValue(const GuideHandle &h) { return h.first == Qt::Horizontal ? guidesConfig.horizontalGuideLines()[h.second] : guidesConfig.verticalGuideLines()[h.second]; } void KisGuidesManager::Private::setGuideValue(const GuideHandle &h, qreal value) { if (h.first == Qt::Horizontal) { QList guides = guidesConfig.horizontalGuideLines(); guides[h.second] = value; guidesConfig.setHorizontalGuideLines(guides); } else { QList guides = guidesConfig.verticalGuideLines(); guides[h.second] = value; guidesConfig.setVerticalGuideLines(guides); } } void KisGuidesManager::Private::deleteGuide(const GuideHandle &h) { if (h.first == Qt::Horizontal) { QList guides = guidesConfig.horizontalGuideLines(); guides.removeAt(h.second); guidesConfig.setHorizontalGuideLines(guides); } else { QList guides = guidesConfig.verticalGuideLines(); guides.removeAt(h.second); guidesConfig.setVerticalGuideLines(guides); } } bool KisGuidesManager::Private::updateCursor(const QPointF &docPos) { KisCanvas2 *canvas = view->canvasBase(); const GuideHandle guide = findGuide(docPos); const bool guideValid = isGuideValid(guide); if (guideValid && !cursorSwitched) { oldCursor = canvas->canvasWidget()->cursor(); } if (guideValid) { cursorSwitched = true; QCursor newCursor = guide.first == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor; canvas->canvasWidget()->setCursor(newCursor); } if (!guideValid && cursorSwitched) { canvas->canvasWidget()->setCursor(oldCursor); cursorSwitched = false; } return guideValid; } void KisGuidesManager::Private::initDragStart(const GuideHandle &guide, const QPointF &dragStart, qreal guideValue, bool snapToStart) { currentGuide = guide; dragStartDoc = dragStart; dragStartGuidePos = guideValue; dragPointerOffset = guide.first == Qt::Horizontal ? QPointF(0, dragStartGuidePos - dragStartDoc.y()) : QPointF(dragStartGuidePos - dragStartDoc.x(), 0); KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); snapGuide->reset(); if (snapToStart) { KisSnapLineStrategy *strategy = new KisSnapLineStrategy(); strategy->addLine(guide.first, guideValue); snapGuide->addCustomSnapStrategy(strategy); } } QPointF KisGuidesManager::Private::alignToPixels(const QPointF docPoint) { KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); QPoint imagePoint = converter->documentToImage(docPoint).toPoint(); return converter->imageToDocument(imagePoint); } bool KisGuidesManager::Private::mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers) { if (isGuideValid(currentGuide)) { KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); const QPointF snappedPos = snapGuide->snap(docPos, dragPointerOffset, modifiers); const QPointF offset = snappedPos - dragStartDoc; const qreal newValue = dragStartGuidePos + (currentGuide.first == Qt::Horizontal ? offset.y() : offset.x()); setGuideValue(currentGuide, newValue); q->setGuidesConfigImpl(guidesConfig); } return updateCursor(docPos); } bool KisGuidesManager::Private::mouseReleaseHandler(const QPointF &docPos) { bool result = false; KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); if (isGuideValid(currentGuide)) { const QRectF docRect = converter->imageRectInDocumentPixels(); // TODO: enable work rect after we fix painting guides // outside canvas in openGL mode const QRectF workRect = KisAlgebra2D::blowRect(docRect, 0 /*0.2*/); if (!workRect.contains(docPos)) { deleteGuide(currentGuide); q->setGuidesConfigImpl(guidesConfig); /** * When we delete a guide, it might happen that we are * delting the last guide. Therefore we should eat the * corresponding event so that the event filter would stop * the filter processing. */ result = true; } currentGuide = invalidGuide; dragStartDoc = QPointF(); dragPointerOffset = QPointF(); dragStartGuidePos = 0; KoSnapGuide *snapGuide = view->canvasBase()->snapGuide(); snapGuide->reset(); updateSnappingStatus(guidesConfig); } return updateCursor(docPos) | result; } QPointF KisGuidesManager::Private::getDocPointFromEvent(QEvent *event) { QPointF result; KisCanvas2 *canvas = view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); result = alignToPixels(converter->widgetToDocument(mouseEvent->pos())); } else if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *tabletEvent = static_cast(event); result = alignToPixels(converter->widgetToDocument(tabletEvent->pos())); } return result; } Qt::MouseButton KisGuidesManager::Private::getButtonFromEvent(QEvent *event) { Qt::MouseButton button = Qt::NoButton; if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); button = mouseEvent->button(); } else if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *tabletEvent = static_cast(event); button = tabletEvent->button(); } return button; } bool KisGuidesManager::eventFilter(QObject *obj, QEvent *event) { if (!m_d->view || obj != m_d->view->canvasBase()->canvasWidget()) return false; bool retval = false; switch (event->type()) { case QEvent::Enter: case QEvent::Leave: case QEvent::TabletMove: case QEvent::MouseMove: { const QPointF docPos = m_d->getDocPointFromEvent(event); const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers(); retval = m_d->mouseMoveHandler(docPos, modifiers); break; } case QEvent::TabletPress: case QEvent::MouseButtonPress: { if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break; const QPointF docPos = m_d->getDocPointFromEvent(event); const Private::GuideHandle guide = m_d->findGuide(docPos); const bool guideValid = m_d->isGuideValid(guide); if (guideValid) { m_d->initDragStart(guide, docPos, m_d->guideValue(guide), true); } retval = m_d->updateCursor(docPos); break; } case QEvent::TabletRelease: case QEvent::MouseButtonRelease: { if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break; const QPointF docPos = m_d->getDocPointFromEvent(event); retval = m_d->mouseReleaseHandler(docPos); break; } default: break; } return !retval ? QObject::eventFilter(obj, event) : true; } void KisGuidesManager::slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos) { if (m_d->guidesConfig.lockGuides()) return; KisCanvas2 *canvas = m_d->view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos)); if (m_d->isGuideValid(m_d->currentGuide)) { const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers(); m_d->mouseMoveHandler(docPos, modifiers); } else { m_d->guidesConfig.setShowGuides(true); if (orientation == Qt::Horizontal) { QList guides = m_d->guidesConfig.horizontalGuideLines(); guides.append(docPos.y()); m_d->currentGuide.first = orientation; m_d->currentGuide.second = guides.size() - 1; m_d->guidesConfig.setHorizontalGuideLines(guides); m_d->initDragStart(m_d->currentGuide, docPos, docPos.y(), false); } else { QList guides = m_d->guidesConfig.verticalGuideLines(); guides.append(docPos.x()); m_d->currentGuide.first = orientation; m_d->currentGuide.second = guides.size() - 1; m_d->guidesConfig.setVerticalGuideLines(guides); m_d->initDragStart(m_d->currentGuide, docPos, docPos.x(), false); } setGuidesConfigImpl(m_d->guidesConfig); } } void KisGuidesManager::slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos) { Q_UNUSED(orientation); if (m_d->guidesConfig.lockGuides()) return; KisCanvas2 *canvas = m_d->view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos)); m_d->mouseReleaseHandler(docPos); } QAction* KisGuidesManager::Private::createShortenedAction(const QString &text, const QString &parentId, QObject *parent) { KisActionManager *actionManager = view->viewManager()->actionManager(); QAction *action = 0; KisAction *parentAction = 0; action = new QAction(text, parent); action->setCheckable(true); parentAction = actionManager->actionByName(parentId); action->setChecked(parentAction->isChecked()); connect(action, SIGNAL(toggled(bool)), parentAction, SLOT(setChecked(bool))); return action; } void KisGuidesManager::slotShowSnapOptions() { const QPoint pos = QCursor::pos(); QMenu menu; menu.addSection(i18n("Snap to:")); menu.addAction(m_d->createShortenedAction(i18n("Grid"), "view_snap_to_grid", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Guides"), "view_snap_to_guides", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Orthogonal"), "view_snap_orthogonal", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Node"), "view_snap_node", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Extension"), "view_snap_extension", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Intersection"), "view_snap_intersection", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Bounding Box"), "view_snap_bounding_box", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Image Bounds"), "view_snap_image_bounds", &menu)); menu.addAction(m_d->createShortenedAction(i18n("Image Center"), "view_snap_image_center", &menu)); menu.exec(pos); } void KisGuidesManager::setSnapOrthogonal(bool value) { m_d->snapConfig.setOrthogonal(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapNode(bool value) { m_d->snapConfig.setNode(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapExtension(bool value) { m_d->snapConfig.setExtension(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapIntersection(bool value) { m_d->snapConfig.setIntersection(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapBoundingBox(bool value) { m_d->snapConfig.setBoundingBox(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapImageBounds(bool value) { m_d->snapConfig.setImageBounds(value); m_d->updateSnappingStatus(m_d->guidesConfig); } void KisGuidesManager::setSnapImageCenter(bool value) { m_d->snapConfig.setImageCenter(value); m_d->updateSnappingStatus(m_d->guidesConfig); } diff --git a/libs/ui/canvas/kis_guides_manager.h b/libs/ui/canvas/kis_guides_manager.h index 3e670587bc..9c7aadea61 100644 --- a/libs/ui/canvas/kis_guides_manager.h +++ b/libs/ui/canvas/kis_guides_manager.h @@ -1,84 +1,90 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_GUIDES_MANAGER_H #define __KIS_GUIDES_MANAGER_H #include #include #include "kritaui_export.h" +#include class KisView; class KisActionManager; class KisCanvasDecoration; class KisGuidesConfig; class KRITAUI_EXPORT KisGuidesManager : public QObject { Q_OBJECT public: KisGuidesManager(QObject *parent = 0); ~KisGuidesManager() override; void setup(KisActionManager *actionManager); void setView(QPointer view); bool showGuides() const; bool lockGuides() const; bool snapToGuides() const; + bool rulersMultiple2() const; + + KoUnit::Type unitType() const; bool eventFilter(QObject *obj, QEvent *event) override; Q_SIGNALS: void sigRequestUpdateGuidesConfig(const KisGuidesConfig &config); public Q_SLOTS: void setGuidesConfig(const KisGuidesConfig &config); void slotDocumentRequestedConfig(const KisGuidesConfig &config); void setShowGuides(bool value); void setLockGuides(bool value); void setSnapToGuides(bool value); + void setRulersMultiple2(bool value); + void setUnitType(KoUnit::Type type); void slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos); void slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos); void slotShowSnapOptions(); void setSnapOrthogonal(bool value); void setSnapNode(bool value); void setSnapExtension(bool value); void setSnapIntersection(bool value); void setSnapBoundingBox(bool value); void setSnapImageBounds(bool value); void setSnapImageCenter(bool value); void slotUploadConfigToDocument(); private: void setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified = true); void attachEventFilterImpl(bool value); void syncActionsStatus(); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_GUIDES_MANAGER_H */ diff --git a/libs/ui/input/kis_zoom_action.cpp b/libs/ui/input/kis_zoom_action.cpp index 97d3d5de08..a61857359c 100644 --- a/libs/ui/input/kis_zoom_action.cpp +++ b/libs/ui/input/kis_zoom_action.cpp @@ -1,304 +1,304 @@ /* This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_zoom_action.h" #include #include #include #include #include #include #include #include "kis_cursor.h" #include "KisViewManager.h" #include "kis_input_manager.h" #include "kis_config.h" inline QPoint pointFromEvent(QEvent *event) { if (!event) { return QPoint(); } else if (QMouseEvent *mouseEvent = dynamic_cast(event)) { return mouseEvent->pos(); } else if (QTabletEvent *tabletEvent = dynamic_cast(event)) { return tabletEvent->pos(); } else if (QWheelEvent *wheelEvent = dynamic_cast(event)) { return wheelEvent->pos(); } return QPoint(); } class KisZoomAction::Private { public: Private(KisZoomAction *qq) : q(qq), distance(0), lastDistance(0.f) {} QPointF centerPoint(QTouchEvent* event); KisZoomAction *q; int distance; Shortcuts mode; QPointF lastPosition; float lastDistance; QPoint startPoint; void zoomTo(bool zoomIn, const QPoint &pos); }; QPointF KisZoomAction::Private::centerPoint(QTouchEvent* event) { QPointF result; int count = 0; Q_FOREACH (QTouchEvent::TouchPoint point, event->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { - result += point.screenPos(); + result += point.pos(); count++; } } if (count > 0) { return result / count; } else { return QPointF(); } } void KisZoomAction::Private::zoomTo(bool zoomIn, const QPoint &point) { KoZoomAction *zoomAction = q->inputManager()->canvas()->viewManager()->zoomController()->zoomAction(); if (!point.isNull()) { float oldZoom = zoomAction->effectiveZoom(); float newZoom = zoomIn ? zoomAction->nextZoomLevel() : zoomAction->prevZoomLevel(); KoCanvasControllerWidget *controller = dynamic_cast( q->inputManager()->canvas()->canvasController()); controller->zoomRelativeToPoint(point, newZoom / oldZoom); } else { if (zoomIn) { zoomAction->zoomIn(); } else { zoomAction->zoomOut(); } } } KisZoomAction::KisZoomAction() : KisAbstractInputAction("Zoom Canvas") , d(new Private(this)) { setName(i18n("Zoom Canvas")); setDescription(i18n("The Zoom Canvas action zooms the canvas.")); QHash< QString, int > shortcuts; shortcuts.insert(i18n("Zoom Mode"), ZoomModeShortcut); shortcuts.insert(i18n("Discrete Zoom Mode"), DiscreteZoomModeShortcut); shortcuts.insert(i18n("Relative Zoom Mode"), RelativeZoomModeShortcut); shortcuts.insert(i18n("Relative Discrete Zoom Mode"), RelativeDiscreteZoomModeShortcut); shortcuts.insert(i18n("Zoom In"), ZoomInShortcut); shortcuts.insert(i18n("Zoom Out"), ZoomOutShortcut); shortcuts.insert(i18n("Reset Zoom to 100%"), ZoomResetShortcut); shortcuts.insert(i18n("Fit to Page"), ZoomToPageShortcut); shortcuts.insert(i18n("Fit to Width"), ZoomToWidthShortcut); setShortcutIndexes(shortcuts); } KisZoomAction::~KisZoomAction() { delete d; } int KisZoomAction::priority() const { return 4; } void KisZoomAction::activate(int shortcut) { if (shortcut == DiscreteZoomModeShortcut || shortcut == RelativeDiscreteZoomModeShortcut) { QApplication::setOverrideCursor(KisCursor::zoomDiscreteCursor()); } else /* if (shortcut == SmoothZoomModeShortcut) */ { QApplication::setOverrideCursor(KisCursor::zoomSmoothCursor()); } } void KisZoomAction::deactivate(int shortcut) { Q_UNUSED(shortcut); QApplication::restoreOverrideCursor(); } void KisZoomAction::begin(int shortcut, QEvent *event) { KisAbstractInputAction::begin(shortcut, event); d->lastDistance = 0.f; switch(shortcut) { case ZoomModeShortcut: case RelativeZoomModeShortcut: { d->startPoint = pointFromEvent(event); d->mode = (Shortcuts)shortcut; QTouchEvent *tevent = dynamic_cast(event); if(tevent) d->lastPosition = d->centerPoint(tevent); break; } case DiscreteZoomModeShortcut: case RelativeDiscreteZoomModeShortcut: d->startPoint = pointFromEvent(event); d->mode = (Shortcuts)shortcut; d->distance = 0; break; case ZoomInShortcut: d->zoomTo(true, pointFromEvent(event)); break; case ZoomOutShortcut: d->zoomTo(false, pointFromEvent(event)); break; case ZoomResetShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); break; case ZoomToPageShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_PAGE, 1.0); break; case ZoomToWidthShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0); break; } } void KisZoomAction::inputEvent( QEvent* event ) { switch (event->type()) { case QEvent::TouchUpdate: { QTouchEvent *tevent = static_cast(event); QPointF center = d->centerPoint(tevent); int count = 0; float dist = 0.0f; Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { count++; - dist += (point.screenPos() - center).manhattanLength(); + dist += (point.pos() - center).manhattanLength(); } } dist /= count; float delta = qFuzzyCompare(1.0f, 1.0f + d->lastDistance) ? 1.f : dist / d->lastDistance; if(qAbs(delta) > 0.1f) { qreal zoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); Q_UNUSED(zoom); static_cast(inputManager()->canvas()->canvasController())->zoomRelativeToPoint(center.toPoint(), delta); d->lastDistance = dist; // Also do panning here, as doing it later requires a further check for validity QPointF moveDelta = center - d->lastPosition; inputManager()->canvas()->canvasController()->pan(-moveDelta.toPoint()); d->lastPosition = center; } return; // Don't try to update the cursor during a pinch-zoom } case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); if (gevent->gestureType() == Qt::ZoomNativeGesture) { KisCanvas2 *canvas = inputManager()->canvas(); KisCanvasController *controller = static_cast(canvas->canvasController()); const float delta = 1.0f + gevent->value(); controller->zoomRelativeToPoint(canvas->canvasWidget()->mapFromGlobal(gevent->globalPos()), delta); } else if (gevent->gestureType() == Qt::SmartZoomNativeGesture) { KisCanvas2 *canvas = inputManager()->canvas(); KoZoomController *controller = canvas->viewManager()->zoomController(); if (controller->zoomMode() != KoZoomMode::ZOOM_WIDTH) { controller->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0); } else { controller->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); } } return; } default: break; } KisAbstractInputAction::inputEvent(event); } void KisZoomAction::cursorMoved(const QPointF &lastPos, const QPointF &pos) { QPointF diff = -(pos - lastPos); const int stepCont = 100; const int stepDisc = 20; if (d->mode == ZoomModeShortcut || d->mode == RelativeZoomModeShortcut) { KisConfig cfg; float coeff; if (cfg.readEntry("InvertMiddleClickZoom", false)) { coeff = 1.0 - qreal(diff.y()) / stepCont; } else { coeff = 1.0 + qreal(diff.y()) / stepCont; } if (d->mode == ZoomModeShortcut) { float zoom = coeff * inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, zoom); } else { KoCanvasControllerWidget *controller = dynamic_cast( inputManager()->canvas()->canvasController()); controller->zoomRelativeToPoint(d->startPoint, coeff); } } else if (d->mode == DiscreteZoomModeShortcut || d->mode == RelativeDiscreteZoomModeShortcut) { d->distance += diff.y(); QPoint stillPoint = d->mode == RelativeDiscreteZoomModeShortcut ? d->startPoint : QPoint(); bool zoomIn = d->distance > 0; while (qAbs(d->distance) > stepDisc) { d->zoomTo(zoomIn, stillPoint); d->distance += zoomIn ? -stepDisc : stepDisc; } } } bool KisZoomAction::isShortcutRequired(int shortcut) const { return shortcut == ZoomModeShortcut; } diff --git a/libs/ui/input/wintab/kis_tablet_support_win8.cpp b/libs/ui/input/wintab/kis_tablet_support_win8.cpp index 53bb88a43d..771e5af04f 100644 --- a/libs/ui/input/wintab/kis_tablet_support_win8.cpp +++ b/libs/ui/input/wintab/kis_tablet_support_win8.cpp @@ -1,994 +1,1081 @@ /* * Copyright (c) 2017 Alvin Wong * * 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. */ // Get Windows 8 API prototypes and types #ifdef WINVER # undef WINVER #endif #ifdef _WIN32_WINNT # undef _WIN32_WINNT #endif #define WINVER 0x0602 #define _WIN32_WINNT 0x0602 #include "kis_tablet_support_win8.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_OS_WIN # error This file must not be compiled for non-Windows systems #endif namespace { class Win8PointerInputApi { #define WIN8_POINTER_INPUT_API_LIST(FUNC) \ /* Pointer Input Functions */ \ FUNC(GetPointerPenInfo) \ FUNC(GetPointerPenInfoHistory) \ FUNC(GetPointerType) \ /* Pointer Device Functions */ \ /*FUNC(GetPointerDevices)*/ \ /*FUNC(GetPointerDeviceProperties)*/ \ FUNC(GetPointerDevice) \ FUNC(GetPointerDeviceRects) \ /*FUNC(RegisterPointerDeviceNotifications)*/ \ /* end */ bool m_loaded; public: #define DEFINE_FP_FROM_WINAPI(func) \ public: using p ## func ## _t = std::add_pointer::type; \ private: p ## func ## _t m_p ## func = nullptr; \ public: const p ## func ## _t &func = m_p ## func; // const fp ref to member WIN8_POINTER_INPUT_API_LIST(DEFINE_FP_FROM_WINAPI) #undef DEFINE_FP_FROM_WINAPI public: Win8PointerInputApi() : m_loaded(false) { } bool init() { if (m_loaded) { return true; } QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return false; } #define LOAD_AND_CHECK_FP_FROM_WINAPI(func) \ m_p ## func = reinterpret_cast

(user32Lib.resolve(#func)); \ if (!m_p ## func) { \ dbgTablet << "Failed to load function " #func " from user32.dll"; \ return false; \ } WIN8_POINTER_INPUT_API_LIST(LOAD_AND_CHECK_FP_FROM_WINAPI) #undef LOAD_AND_CHECK_FP_FROM_WINAPI dbgTablet << "Loaded Windows 8 Pointer Input API functions"; m_loaded = true; return true; } bool isLoaded() { return m_loaded; } #undef WIN8_POINTER_INPUT_API_LIST }; // class Win8PointerInputApi Win8PointerInputApi api; class PointerFlagsWrapper { const POINTER_FLAGS f; public: PointerFlagsWrapper(POINTER_FLAGS flags) : f(flags) {} static PointerFlagsWrapper fromPointerInfo(const POINTER_INFO &pointerInfo) { return PointerFlagsWrapper(pointerInfo.pointerFlags); } static PointerFlagsWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) { return fromPointerInfo(penInfo.pointerInfo); } bool isNew() const { return f & POINTER_FLAG_NEW; } bool isInRange() const { return f & POINTER_FLAG_INRANGE; } bool isInContact() const { return f & POINTER_FLAG_INCONTACT; } bool isFirstButtonDown() const { return f & POINTER_FLAG_FIRSTBUTTON; } bool isSecondButtonDown() const { return f & POINTER_FLAG_SECONDBUTTON; } bool isThirdButtonDown() const { return f & POINTER_FLAG_THIRDBUTTON; } bool isForthButtonDown() const { return f & POINTER_FLAG_FOURTHBUTTON; } bool isFifthButtonDown() const { return f & POINTER_FLAG_FIFTHBUTTON; } bool isPrimary() const { return f & POINTER_FLAG_PRIMARY; } bool isConfidence() const { return f & POINTER_FLAG_CONFIDENCE; } bool isCancelled() const { return f & POINTER_FLAG_CANCELED; } bool isDown() const { return f & POINTER_FLAG_DOWN; } bool isUpdate() const { return f & POINTER_FLAG_UPDATE; } bool isUp() const { return f & POINTER_FLAG_UP; } bool isWheel() const { return f & POINTER_FLAG_WHEEL; } bool isHWheel() const { return f & POINTER_FLAG_HWHEEL; } bool isCaptureChanged() const { return f & POINTER_FLAG_CAPTURECHANGED; } bool hasTransform() const { // mingw-w64 headers is missing this flag // return f & POINTER_FLAG_HASTRANSFORM; return f & 0x00400000; } }; // class PointerFlagsWrapper class PenFlagsWrapper { const PEN_FLAGS f; public: PenFlagsWrapper(PEN_FLAGS flags) : f(flags) {} static PenFlagsWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) { return PenFlagsWrapper(penInfo.penFlags); } bool isBarrelPressed() const { return f & PEN_FLAG_BARREL; } bool isInverted() const { return f & PEN_FLAG_INVERTED; } bool isEraserPressed() const { return f & PEN_FLAG_ERASER; } }; // class PenFlagsWrapper class PenMaskWrapper { const PEN_MASK f; public: PenMaskWrapper(PEN_MASK mask) :f(mask) {} static PenMaskWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) { return PenMaskWrapper(penInfo.penMask); } bool pressureValid() const { return f & PEN_MASK_PRESSURE; } bool rotationValid() const { return f & PEN_MASK_ROTATION; } bool tiltXValid() const { return f & PEN_MASK_TILT_X; } bool tiltYValid() const { return f & PEN_MASK_TILT_Y; } }; // class PenMaskWrapper struct PointerDeviceItem { // HANDLE handle; // RECT pointerDeviceRect; // RECT displayRect; qreal himetricToPixelX; qreal himetricToPixelY; qreal pixelOffsetX; qreal pixelOffsetY; DISPLAYCONFIG_ROTATION deviceOrientation; // This is needed to fix tilt }; QHash penDevices; struct PenPointerItem { // int pointerId; // POINTER_PEN_INFO penInfo; HWND hwnd; HANDLE deviceHandle; QPointer activeWidget; // Current widget receiving events qreal oneOverDpr; // 1 / devicePixelRatio of activeWidget bool widgetIsCaptured; // Current widget is capturing a pen cown event bool widgetIsIgnored; // Pen events should be ignored until pen up + bool widgetAcceptsPenEvent; // Whether the widget accepts pen events bool isCaptured() const { return widgetIsCaptured; } }; QHash penPointers; // int primaryPenPointerId; bool handlePointerMsg(const MSG &msg); // extern "C" { // // LRESULT CALLBACK pointerDeviceNotificationsWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) // { // switch (uMsg) { // case WM_POINTERDEVICECHANGE: // dbgTablet << "I would want to handle this WM_POINTERDEVICECHANGE event, but ms just doesn't want me to use it"; // dbgTablet << " wParam:" << wParam; // dbgTablet << " lParam:" << lParam; // return 0; // case WM_POINTERDEVICEINRANGE: // dbgTablet << "I would want to handle this WM_POINTERDEVICEINRANGE event, but ms just doesn't want me to use it"; // dbgTablet << " wParam:" << wParam; // dbgTablet << " lParam:" << lParam; // return 0; // case WM_POINTERDEVICEOUTOFRANGE: // dbgTablet << "I would want to handle this WM_POINTERDEVICEOUTOFRANGE event, but ms just doesn't want me to use it"; // dbgTablet << " wParam:" << wParam; // dbgTablet << " lParam:" << lParam; // return 0; // } // return DefWindowProcW(hwnd, uMsg, wParam, lParam); // } // // } // extern "C" } // namespace bool KisTabletSupportWin8::isAvailable() { // Just try loading the APIs return api.init(); } bool KisTabletSupportWin8::init() { return api.init(); } // void KisTabletSupportWin8::registerPointerDeviceNotifications() // { // const wchar_t *className = L"w8PointerMsgWindow"; // HINSTANCE hInst = static_cast(GetModuleHandleW(nullptr)); // WNDCLASSEXW wc; // wc.cbSize = sizeof(WNDCLASSEXW); // wc.style = 0; // wc.lpfnWndProc = pointerDeviceNotificationsWndProc; // wc.cbClsExtra = 0; // wc.cbWndExtra = 0; // wc.hInstance = hInst; // wc.hCursor = 0; // wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW); // wc.hIcon = 0; // wc.hIconSm = 0; // wc.lpszMenuName = 0; // wc.lpszClassName = className; // // if (RegisterClassEx(&wc)) { // HWND hwnd = CreateWindowEx(0, className, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, hInst, nullptr); // api.RegisterPointerDeviceNotifications(hwnd, TRUE); // } else { // dbgTablet << "Cannot register dummy window"; // } // } bool KisTabletSupportWin8::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { if (!result) { // I don't know why this even happens, but it actually does // And the same event is sent in again with result != nullptr return false; } // This is only installed on Windows so there is no reason to check eventType MSG &msg = *static_cast(message); switch (msg.message) { case WM_POINTERDOWN: case WM_POINTERUP: case WM_POINTERENTER: case WM_POINTERLEAVE: case WM_POINTERUPDATE: case WM_POINTERCAPTURECHANGED: { bool handled = handlePointerMsg(msg); if (handled) { *result = 0; return true; } break; } case WM_TABLET_QUERYSYSTEMGESTURESTATUS: *result = 0; return true; } Q_UNUSED(eventType) return false; } namespace { QDebug operator<<(QDebug debug, const POINT &pt) { QDebugStateSaver saver(debug); debug.nospace() << '(' << pt.x << ", " << pt.y << ')'; return debug; } QDebug operator<<(QDebug debug, const RECT &rect) { QDebugStateSaver saver(debug); debug.nospace() << '(' << rect.left << ", " << rect.top << ", " << rect.right << ", " << rect.bottom << ')'; return debug; } bool registerOrUpdateDevice(HANDLE deviceHandle, const RECT &pointerDeviceRect, const RECT &displayRect, const DISPLAYCONFIG_ROTATION deviceOrientation) { bool isPreviouslyRegistered = penDevices.contains(deviceHandle); PointerDeviceItem &deviceItem = penDevices[deviceHandle]; PointerDeviceItem oldDeviceItem = deviceItem; // deviceItem.handle = deviceHandle; deviceItem.himetricToPixelX = static_cast(displayRect.right - displayRect.left) / (pointerDeviceRect.right - pointerDeviceRect.left); deviceItem.himetricToPixelY = static_cast(displayRect.bottom - displayRect.top) / (pointerDeviceRect.bottom - pointerDeviceRect.top); deviceItem.pixelOffsetX = static_cast(displayRect.left) - deviceItem.himetricToPixelX * pointerDeviceRect.left; deviceItem.pixelOffsetY = static_cast(displayRect.top) - deviceItem.himetricToPixelY * pointerDeviceRect.top; deviceItem.deviceOrientation = deviceOrientation; if (!isPreviouslyRegistered) { dbgTablet << "Registered pen device" << deviceHandle << "with displayRect" << displayRect << "and deviceRect" << pointerDeviceRect << "scale" << deviceItem.himetricToPixelX << deviceItem.himetricToPixelY << "offset" << deviceItem.pixelOffsetX << deviceItem.pixelOffsetY << "orientation" << deviceItem.deviceOrientation; } else if (deviceItem.himetricToPixelX != oldDeviceItem.himetricToPixelX || deviceItem.himetricToPixelY != oldDeviceItem.himetricToPixelY || deviceItem.pixelOffsetX != oldDeviceItem.pixelOffsetX || deviceItem.pixelOffsetY != oldDeviceItem.pixelOffsetY || deviceItem.deviceOrientation != oldDeviceItem.deviceOrientation) { dbgTablet << "Updated pen device" << deviceHandle << "with displayRect" << displayRect << "and deviceRect" << pointerDeviceRect << "scale" << deviceItem.himetricToPixelX << deviceItem.himetricToPixelY << "offset" << deviceItem.pixelOffsetX << deviceItem.pixelOffsetY << "orientation" << deviceItem.deviceOrientation; } return true; } bool registerOrUpdateDevice(HANDLE deviceHandle) { RECT pointerDeviceRect, displayRect; if (!api.GetPointerDeviceRects(deviceHandle, &pointerDeviceRect, &displayRect)) { dbgTablet << "GetPointerDeviceRects failed"; return false; } POINTER_DEVICE_INFO pointerDeviceInfo; if (!api.GetPointerDevice(deviceHandle, &pointerDeviceInfo)) { dbgTablet << "GetPointerDevice failed"; return false; } return registerOrUpdateDevice(deviceHandle, pointerDeviceRect, displayRect, static_cast(pointerDeviceInfo.displayOrientation)); } QTabletEvent makeProximityTabletEvent(const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo) { PenFlagsWrapper penFlags = PenFlagsWrapper::fromPenInfo(penInfo); QTabletEvent::PointerType pointerType = penFlags.isInverted() ? QTabletEvent::Eraser : QTabletEvent::Pen; const QPointF emptyPoint; return QTabletEvent( eventType, // type emptyPoint, // pos emptyPoint, // globalPos QTabletEvent::Stylus, // device pointerType, // pointerType 0, // pressure 0, // xTilt 0, // yTilt 0, // tangentialPressure 0, // rotation 0, // z Qt::NoModifier, // keyState reinterpret_cast(penInfo.pointerInfo.sourceDevice), // uniqueID Qt::NoButton, // button (Qt::MouseButtons)0 // buttons ); } // void rotateTiltAngles(int &tiltX, int &tiltY, const DISPLAYCONFIG_ROTATION orientation) { // int newTiltX, newTiltY; // switch (orientation) { // case DISPLAYCONFIG_ROTATION_ROTATE90: // newTiltX = -tiltY; // newTiltY = tiltX; // break; // case DISPLAYCONFIG_ROTATION_ROTATE180: // newTiltX = -tiltX; // newTiltY = -tiltY; // break; // case DISPLAYCONFIG_ROTATION_ROTATE270: // newTiltX = tiltY; // newTiltY = -tiltX; // break; // case DISPLAYCONFIG_ROTATION_IDENTITY: // default: // newTiltX = tiltX; // newTiltY = tiltY; // break; // } // tiltX = newTiltX; // tiltY = newTiltY; // } QTabletEvent makePositionalTabletEvent(const QWidget *targetWidget, const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo, const PointerDeviceItem &deviceItem, const PenPointerItem &penPointerItem) { PenFlagsWrapper penFlags = PenFlagsWrapper::fromPenInfo(penInfo); PointerFlagsWrapper pointerFlags = PointerFlagsWrapper::fromPenInfo(penInfo); PenMaskWrapper penMask = PenMaskWrapper::fromPenInfo(penInfo); const QPointF globalPosF( (deviceItem.himetricToPixelX * penInfo.pointerInfo.ptHimetricLocationRaw.x + deviceItem.pixelOffsetX) * penPointerItem.oneOverDpr, (deviceItem.himetricToPixelY * penInfo.pointerInfo.ptHimetricLocationRaw.y + deviceItem.pixelOffsetY) * penPointerItem.oneOverDpr ); const QPoint globalPos = globalPosF.toPoint(); const QPoint localPos = targetWidget->mapFromGlobal(globalPos); const QPointF delta = globalPosF - globalPos; const QPointF localPosF = localPos + delta; const QTabletEvent::PointerType pointerType = penFlags.isInverted() ? QTabletEvent::Eraser : QTabletEvent::Pen; Qt::MouseButton mouseButton; if (eventType == QEvent::TabletPress) { if (penInfo.pointerInfo.ButtonChangeType == POINTER_CHANGE_SECONDBUTTON_DOWN) { mouseButton = Qt::RightButton; } else { KIS_SAFE_ASSERT_RECOVER(penInfo.pointerInfo.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_DOWN) { qWarning() << "WM_POINTER* sent unknown ButtonChangeType" << penInfo.pointerInfo.ButtonChangeType; } mouseButton = Qt::LeftButton; } } else if (eventType == QEvent::TabletRelease) { if (penInfo.pointerInfo.ButtonChangeType == POINTER_CHANGE_SECONDBUTTON_UP) { mouseButton = Qt::RightButton; } else { KIS_SAFE_ASSERT_RECOVER(penInfo.pointerInfo.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_UP) { qWarning() << "WM_POINTER* sent unknown ButtonChangeType" << penInfo.pointerInfo.ButtonChangeType; } mouseButton = Qt::LeftButton; } } else { mouseButton = Qt::NoButton; } Qt::MouseButtons mouseButtons; if (pointerFlags.isFirstButtonDown()) { mouseButtons |= Qt::LeftButton; } if (pointerFlags.isSecondButtonDown()) { mouseButtons |= Qt::RightButton; } int tiltX = 0, tiltY = 0; if (penMask.tiltXValid()) { tiltX = qBound(-60, penInfo.tiltX, 60); } if (penMask.tiltYValid()) { tiltY = qBound(-60, penInfo.tiltY, 60); } // rotateTiltAngles(tiltX, tiltY, deviceItem.deviceOrientation); int rotation = 0; if (penMask.rotationValid()) { rotation = 360 - penInfo.rotation; // Flip direction and convert to signed int if (rotation > 180) { rotation -= 360; } } return QTabletEvent( eventType, // type localPosF, // pos globalPosF, // globalPos QTabletEvent::Stylus, // device pointerType, // pointerType penMask.pressureValid() ? static_cast(penInfo.pressure) / 1024 : 0, // pressure tiltX, // xTilt tiltY, // yTilt 0, // tangentialPressure rotation, // rotation 0, // z QApplication::queryKeyboardModifiers(), // keyState reinterpret_cast(penInfo.pointerInfo.sourceDevice), // uniqueID mouseButton, // button mouseButtons // buttons ); } bool sendProximityTabletEvent(const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE( eventType == QEvent::TabletEnterProximity || eventType == QEvent::TabletLeaveProximity, false ); QTabletEvent ev = makeProximityTabletEvent(eventType, penInfo); ev.setAccepted(false); ev.setTimestamp(penInfo.pointerInfo.dwTime); QCoreApplication::sendEvent(qApp, &ev); return ev.isAccepted(); } -bool sendPositionalTabletEvent(QWidget *target, const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo, const PointerDeviceItem &device, const PenPointerItem &penPointerItem) +void synthesizeMouseEvent(const QTabletEvent &ev, const POINTER_PEN_INFO &penInfo) +{ + // Update the cursor position + BOOL result = SetCursorPos(penInfo.pointerInfo.ptPixelLocationRaw.x, penInfo.pointerInfo.ptPixelLocationRaw.y); + if (!result) { + dbgInput << "SetCursorPos failed, err" << GetLastError(); + return; + } + // Send mousebutton down/up events. Windows stores the button state. + DWORD inputDataFlags = 0; + switch (ev.type()) { + case QEvent::TabletPress: + switch (ev.button()) { + case Qt::LeftButton: + inputDataFlags = MOUSEEVENTF_LEFTDOWN; + break; + case Qt::RightButton: + inputDataFlags = MOUSEEVENTF_RIGHTDOWN; + break; + default: + return; + } + break; + case QEvent::TabletRelease: + switch (ev.button()) { + case Qt::LeftButton: + inputDataFlags = MOUSEEVENTF_LEFTUP; + break; + case Qt::RightButton: + inputDataFlags = MOUSEEVENTF_RIGHTUP; + break; + default: + return; + } + break; + case QEvent::TabletMove: + default: + return; + } + INPUT inputData = {}; + inputData.type = INPUT_MOUSE; + inputData.mi.dwFlags = inputDataFlags; + inputData.mi.dwExtraInfo = 0xFF515700 | 0x01; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320%28v=vs.85%29.aspx + UINT result2 = SendInput(1, &inputData, sizeof(inputData)); + if (result2 != 1) { + dbgInput << "SendInput failed, err" << GetLastError(); + return; + } +} + +bool sendPositionalTabletEvent(QWidget *target, const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo, const PointerDeviceItem &device, const PenPointerItem &penPointerItem, const bool shouldSynthesizeMouseEvent) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE( eventType == QEvent::TabletMove || eventType == QEvent::TabletPress || eventType == QEvent::TabletRelease, false ); QTabletEvent ev = makePositionalTabletEvent(target, eventType, penInfo, device, penPointerItem); ev.setAccepted(false); ev.setTimestamp(penInfo.pointerInfo.dwTime); QCoreApplication::sendEvent(target, &ev); - return ev.isAccepted(); + if (!shouldSynthesizeMouseEvent) { + // For pen update with multiple updates, only the last update should + // be used to synthesize a mouse event. + return false; + } + // This is some specialized code to handle synthesizing of mouse events from + // the pen events. Issues being: + // 1. We must accept the pointer down/up and the intermediate update events + // to indicate that we want all the pen pointer events for painting, + // otherwise Windows may do weird stuff and skip passing pointer events. + // 2. Some Qt and Krita code uses QCursor::pos() which calls GetCursorPos to + // get the cursor position. This doesn't work nicely before ver 1709 and + // doesn't work at all on ver 1709 if the pen events are handled, so we + // need some way to nudge the cursor on the OS level. + // It appears that using the same way (as in synthesizeMouseEvent) to nudge + // the cursor does work fine for when painting on canvas (which handles + // the QTabletEvent), but not for other widgets because it introduces a lag + // with mouse move events on move start and immediately after mouse down. + // The resolution is to simulate mouse movement with our own code only for + // handled pen events, which is what the following code does. + if (ev.type() == QEvent::TabletMove && ev.buttons() == Qt::NoButton) { + // Let Windows synthesize mouse hover events + return false; + } + if (ev.type() == QEvent::TabletPress && !ev.isAccepted()) { + // On pen down event, if the widget doesn't handle the event, let + // Windows translate the event to touch, mouse or whatever + return false; + } + if (ev.type() != QEvent::TabletPress && !penPointerItem.widgetAcceptsPenEvent) { + // For other events, if the previous pen down event wasn't handled by + // the widget, continue to let Windows translate the event + return false; + } + // Otherwise, we synthesize our mouse events + synthesizeMouseEvent(ev, penInfo); + return true; // and tell Windows that we do want the pen events. } bool handlePenEnterMsg(const POINTER_PEN_INFO &penInfo) { PointerFlagsWrapper pointerFlags = PointerFlagsWrapper::fromPenInfo(penInfo); if (!pointerFlags.isPrimary()) { // Don't handle non-primary pointer messages for now dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "of device" << penInfo.pointerInfo.sourceDevice << "is not flagged PRIMARY"; return false; } // Update the device scaling factors here // It doesn't cost much to recalculate anyway // This ensures that the screen resolution changes are reflected // WM_POINTERDEVICECHANGE might be useful for this, but its docs are too unclear to use registerOrUpdateDevice(penInfo.pointerInfo.sourceDevice); // TODO: Need a way to remove from device registration when devices are changed // We now only handle one pointer at a time, so just clear the pointer registration penPointers.clear(); int pointerId = penInfo.pointerInfo.pointerId; PenPointerItem penPointerItem; penPointerItem.hwnd = penInfo.pointerInfo.hwndTarget; penPointerItem.deviceHandle = penInfo.pointerInfo.sourceDevice; penPointerItem.activeWidget = nullptr; penPointerItem.oneOverDpr = 1.0; penPointerItem.widgetIsCaptured = false; penPointerItem.widgetIsIgnored = false; + penPointerItem.widgetAcceptsPenEvent = false; // penPointerItem.pointerId = pointerId; penPointers.insert(pointerId, penPointerItem); // primaryPenPointerId = pointerId; // penEnter sendProximityTabletEvent(QEvent::TabletEnterProximity, penInfo); return false; } bool handlePenLeaveMsg(const POINTER_PEN_INFO &penInfo) { if (!penPointers.contains(penInfo.pointerInfo.pointerId)) { dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled"; return false; } if (!penDevices.contains(penInfo.pointerInfo.sourceDevice)) { dbgTablet << "Device is gone from the registration???"; // TODO: re-register device? penPointers.remove(penInfo.pointerInfo.pointerId); return false; } // penLeave sendProximityTabletEvent(QEvent::TabletLeaveProximity, penInfo); penPointers.remove(penInfo.pointerInfo.pointerId); return false; } -bool handleSinglePenUpdate(PenPointerItem &penPointerItem, const POINTER_PEN_INFO &penInfo, const PointerDeviceItem &device) +bool handleSinglePenUpdate(PenPointerItem &penPointerItem, const POINTER_PEN_INFO &penInfo, const PointerDeviceItem &device, const bool shouldSynthesizeMouseEvent) { QWidget *targetWidget; if (penPointerItem.isCaptured()) { if (penPointerItem.widgetIsIgnored) { return false; } targetWidget = penPointerItem.activeWidget; if (!targetWidget) { return false; } } else { QWidget *hwndWidget = QWidget::find(reinterpret_cast(penInfo.pointerInfo.hwndTarget)); if (!hwndWidget) { dbgTablet << "HWND cannot be mapped to QWidget (what?)"; return false; } { // Check popup / modal widget QWidget *modalWidget = QApplication::activePopupWidget(); if (!modalWidget) { modalWidget = QApplication::activeModalWidget(); } if (modalWidget && modalWidget != hwndWidget && !modalWidget->isAncestorOf(hwndWidget)) { return false; } } { QWindow *topLevelWindow = hwndWidget->windowHandle(); if (topLevelWindow) { penPointerItem.oneOverDpr = 1.0 / topLevelWindow->devicePixelRatio(); } else { penPointerItem.oneOverDpr = 1.0 / qApp->devicePixelRatio(); } } QPoint posInHwndWidget = hwndWidget->mapFromGlobal(QPoint( static_cast(penInfo.pointerInfo.ptPixelLocationRaw.x * penPointerItem.oneOverDpr), static_cast(penInfo.pointerInfo.ptPixelLocationRaw.y * penPointerItem.oneOverDpr) )); targetWidget = hwndWidget->childAt(posInHwndWidget); if (!targetWidget) { // dbgTablet << "No childQWidget at cursor position"; targetWidget = hwndWidget; } // penPointerItem.activeWidget = targetWidget; } - bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletMove, penInfo, device, penPointerItem); - if (!handled) { - // dbgTablet << "Target widget doesn't want pen events"; - } + bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletMove, penInfo, device, penPointerItem, shouldSynthesizeMouseEvent); return handled; } bool handlePenUpdateMsg(const POINTER_PEN_INFO &penInfo) { auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId); if (currentPointerIt == penPointers.end()) { // dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled"; return false; } const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice); if (devIt == penDevices.end()) { dbgTablet << "Device not registered???"; return false; } // UINT32 entriesCount = 0; // if (!api.GetPointerPenInfoHistory(penInfo.pointerInfo.pointerId, &entriesCount, nullptr)) { // dbgTablet << "GetPointerPenInfoHistory (getting count) failed"; // return false; // } UINT32 entriesCount = penInfo.pointerInfo.historyCount; // dbgTablet << "entriesCount:" << entriesCount; + bool handled = false; if (entriesCount != 1) { QVector penInfoArray(entriesCount); if (!api.GetPointerPenInfoHistory(penInfo.pointerInfo.pointerId, &entriesCount, penInfoArray.data())) { dbgTablet << "GetPointerPenInfoHistory failed"; return false; } bool handled = false; // The returned array is in reverse chronological order const auto rbegin = penInfoArray.rbegin(); const auto rend = penInfoArray.rend(); + const auto rlast = rend - 1; // Only synthesize mouse event for the last one for (auto it = rbegin; it != rend; ++it) { - handled |= handleSinglePenUpdate(*currentPointerIt, *it, *devIt); // Bitwise OR doesn't short circuit + handled |= handleSinglePenUpdate(*currentPointerIt, *it, *devIt, it == rlast); // Bitwise OR doesn't short circuit } - return handled; } else { - return handleSinglePenUpdate(*currentPointerIt, penInfo, *devIt); + handled = handleSinglePenUpdate(*currentPointerIt, penInfo, *devIt, true); } + return handled; } bool handlePenDownMsg(const POINTER_PEN_INFO &penInfo) { // PointerFlagsWrapper pointerFlags = PointerFlagsWrapper::fromPenInfo(penInfo); // if (!pointerFlags.isPrimary()) { // // Don't handle non-primary pointer messages for now // return false; // } auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId); if (currentPointerIt == penPointers.end()) { dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled"; return false; } currentPointerIt->hwnd = penInfo.pointerInfo.hwndTarget; // They *should* be the same, but just in case QWidget *hwndWidget = QWidget::find(reinterpret_cast(penInfo.pointerInfo.hwndTarget)); if (!hwndWidget) { dbgTablet << "HWND cannot be mapped to QWidget (what?)"; return false; } { QWindow *topLevelWindow = hwndWidget->windowHandle(); if (topLevelWindow) { currentPointerIt->oneOverDpr = 1.0 / topLevelWindow->devicePixelRatio(); } else { currentPointerIt->oneOverDpr = 1.0 / qApp->devicePixelRatio(); } } QPoint posInHwndWidget = hwndWidget->mapFromGlobal(QPoint( static_cast(penInfo.pointerInfo.ptPixelLocationRaw.x * currentPointerIt->oneOverDpr), static_cast(penInfo.pointerInfo.ptPixelLocationRaw.y * currentPointerIt->oneOverDpr) )); QWidget *targetWidget = hwndWidget->childAt(posInHwndWidget); if (!targetWidget) { dbgTablet << "No childQWidget at cursor position"; targetWidget = hwndWidget; } currentPointerIt->activeWidget = targetWidget; currentPointerIt->widgetIsCaptured = true; // dbgTablet << "QWidget" << targetWidget->windowTitle() << "is capturing pointer" << penInfo.pointerInfo.pointerId; { // Check popup / modal widget QWidget *modalWidget = QApplication::activePopupWidget(); if (!modalWidget) { modalWidget = QApplication::activeModalWidget(); } if (modalWidget && modalWidget != hwndWidget && !modalWidget->isAncestorOf(hwndWidget)) { currentPointerIt->widgetIsIgnored = true; dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "is being captured but will be ignored"; return false; } } // penDown const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice); if (devIt == penDevices.end()) { dbgTablet << "Device not registered???"; return false; } - bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletPress, penInfo, *devIt, *currentPointerIt); + bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletPress, penInfo, *devIt, *currentPointerIt, true); + currentPointerIt->widgetAcceptsPenEvent = handled; if (!handled) { // dbgTablet << "QWidget did not handle tablet down event"; } return handled; } bool handlePenUpMsg(const POINTER_PEN_INFO &penInfo) { auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId); if (currentPointerIt == penPointers.end()) { dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled"; return false; } PenPointerItem &penPointerItem = *currentPointerIt; if (!penPointerItem.isCaptured()) { dbgTablet << "Pointer wasn't captured"; return false; } if (penPointerItem.widgetIsIgnored) { penPointerItem.widgetIsCaptured = false; penPointerItem.widgetIsIgnored = false; return false; } // penUp QWidget *targetWidget = penPointerItem.activeWidget; if (!targetWidget) { dbgTablet << "Previously captured target has been deleted"; penPointerItem.widgetIsCaptured = false; return false; } const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice); if (devIt == penDevices.end()) { dbgTablet << "Device not registered???"; return false; } - bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletRelease, penInfo, *devIt, penPointerItem); + bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletRelease, penInfo, *devIt, penPointerItem, true); // dbgTablet << "QWidget" << currentPointerIt->activeWidget->windowTitle() << "is releasing capture to pointer" << penInfo.pointerInfo.pointerId; penPointerItem.widgetIsCaptured = false; + penPointerItem.widgetAcceptsPenEvent = false; return handled; } bool handlePointerMsg(const MSG &msg) { if (!api.isLoaded()) { qWarning() << "Windows 8 Pointer Input API functions not loaded"; return false; } int pointerId = GET_POINTERID_WPARAM(msg.wParam); POINTER_INPUT_TYPE pointerType; if (!api.GetPointerType(pointerId, &pointerType)) { dbgTablet << "GetPointerType failed"; return false; } if (pointerType != PT_PEN) { // dbgTablet << "pointerType" << pointerType << "is not PT_PEN"; return false; } POINTER_PEN_INFO penInfo; if (!api.GetPointerPenInfo(pointerId, &penInfo)) { dbgTablet << "GetPointerPenInfo failed"; return false; } switch (msg.message) { case WM_POINTERDOWN: // dbgTablet << "WM_POINTERDOWN"; break; case WM_POINTERUP: // dbgTablet << "WM_POINTERUP"; break; case WM_POINTERENTER: // dbgTablet << "WM_POINTERENTER"; break; case WM_POINTERLEAVE: // dbgTablet << "WM_POINTERLEAVE"; break; case WM_POINTERUPDATE: // dbgTablet << "WM_POINTERUPDATE"; break; case WM_POINTERCAPTURECHANGED: // dbgTablet << "WM_POINTERCAPTURECHANGED"; break; default: dbgTablet << "I missed this message: " << msg.message; break; } // dbgTablet << " hwnd: " << penInfo.pointerInfo.hwndTarget; // dbgTablet << " msg hwnd: " << msg.hwnd; // dbgTablet << " pointerId: " << pointerId; // dbgTablet << " sourceDevice:" << penInfo.pointerInfo.sourceDevice; // dbgTablet << " pointerFlags:" << penInfo.pointerInfo.pointerFlags; // dbgTablet << " btnChgType: " << penInfo.pointerInfo.ButtonChangeType; // dbgTablet << " penFlags: " << penInfo.penFlags; // dbgTablet << " penMask: " << penInfo.penMask; // dbgTablet << " pressure: " << penInfo.pressure; // dbgTablet << " rotation: " << penInfo.rotation; // dbgTablet << " tiltX: " << penInfo.tiltX; // dbgTablet << " tiltY: " << penInfo.tiltY; // dbgTablet << " ptPixelLocationRaw: " << penInfo.pointerInfo.ptPixelLocationRaw; // dbgTablet << " ptHimetricLocationRaw:" << penInfo.pointerInfo.ptHimetricLocationRaw; // RECT pointerDeviceRect, displayRect; // if (!api.GetPointerDeviceRects(penInfo.pointerInfo.sourceDevice, &pointerDeviceRect, &displayRect)) { // dbgTablet << "GetPointerDeviceRects failed"; // return false; // } // dbgTablet << " pointerDeviceRect:" << pointerDeviceRect; // dbgTablet << " displayRect:" << displayRect; // dbgTablet << " scaled X:" << static_cast(penInfo.pointerInfo.ptHimetricLocationRaw.x) / (pointerDeviceRect.right - pointerDeviceRect.left) * (displayRect.right - displayRect.left); // dbgTablet << " scaled Y:" << static_cast(penInfo.pointerInfo.ptHimetricLocationRaw.y) / (pointerDeviceRect.bottom - pointerDeviceRect.top) * (displayRect.bottom - displayRect.top); switch (msg.message) { case WM_POINTERDOWN: return handlePenDownMsg(penInfo); case WM_POINTERUP: return handlePenUpMsg(penInfo); case WM_POINTERENTER: return handlePenEnterMsg(penInfo); case WM_POINTERLEAVE: return handlePenLeaveMsg(penInfo); case WM_POINTERUPDATE: - // HACK: Force further processing to force Windows to generate mouse move events - handlePenUpdateMsg(penInfo); - return false; + return handlePenUpdateMsg(penInfo); case WM_POINTERCAPTURECHANGED: // TODO: Should this event be handled? dbgTablet << "FIXME: WM_POINTERCAPTURECHANGED isn't handled"; break; } return false; } } // namespace diff --git a/libs/ui/kis_painting_assistant.cc b/libs/ui/kis_painting_assistant.cc index 7617852d40..04c949afe1 100644 --- a/libs/ui/kis_painting_assistant.cc +++ b/libs/ui/kis_painting_assistant.cc @@ -1,682 +1,747 @@ /* * Copyright (c) 2008,2011 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * 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 "kis_painting_assistant.h" #include "kis_coordinates_converter.h" #include "kis_debug.h" #include +#include "kis_tool.h" #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KisPaintingAssistantFactoryRegistry, s_instance) struct KisPaintingAssistantHandle::Private { QList assistants; char handle_type; }; KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private) { } + KisPaintingAssistantHandle::KisPaintingAssistantHandle(QPointF p) : QPointF(p), d(new Private) { } KisPaintingAssistantHandle::KisPaintingAssistantHandle(const KisPaintingAssistantHandle& rhs) : QPointF(rhs) , KisShared() , d(new Private) { } KisPaintingAssistantHandle& KisPaintingAssistantHandle::operator=(const QPointF & pt) { setX(pt.x()); setY(pt.y()); return *this; } void KisPaintingAssistantHandle::setType(char type) { d->handle_type = type; } char KisPaintingAssistantHandle::handleType() { return d->handle_type; } - KisPaintingAssistantHandle::~KisPaintingAssistantHandle() { Q_ASSERT(d->assistants.empty()); delete d; } void KisPaintingAssistantHandle::registerAssistant(KisPaintingAssistant* assistant) { Q_ASSERT(!d->assistants.contains(assistant)); d->assistants.append(assistant); } void KisPaintingAssistantHandle::unregisterAssistant(KisPaintingAssistant* assistant) { d->assistants.removeOne(assistant); Q_ASSERT(!d->assistants.contains(assistant)); } bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant) { return d->assistants.contains(assistant); } void KisPaintingAssistantHandle::mergeWith(KisPaintingAssistantHandleSP handle) { - if(this->handleType()=='S' || handle.data()->handleType()== 'S') + if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) { return; + } + + Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) { if (!assistant->handles().contains(this)) { assistant->replaceHandle(handle, this); } } } void KisPaintingAssistantHandle::uncache() { Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) { assistant->uncache(); } } struct KisPaintingAssistant::Private { QString id; QString name; - bool snapping; + bool isSnappingActive; bool outlineVisible; QList handles,sideHandles; QPixmapCache::Key cached; QRect cachedRect; // relative to boundingRect().topLeft() KisPaintingAssistantHandleSP topLeft, bottomLeft, topRight, bottomRight, topMiddle, bottomMiddle, rightMiddle, leftMiddle; + KisCanvas2* m_canvas = 0; + + struct TranslationInvariantTransform { qreal m11, m12, m21, m22; TranslationInvariantTransform() { } TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { } bool operator==(const TranslationInvariantTransform& b) { return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22; } } cachedTransform; -}; -KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private) -{ - d->id = id; - d->name = name; - d->snapping=true; - d->outlineVisible=true; -} + QColor assistantColor; +}; -bool KisPaintingAssistant::snapping() const +void KisPaintingAssistant::setAssistantColor(QColor color) { - return d->snapping; + d->assistantColor = color; } -void KisPaintingAssistant::setSnapping(bool set) +KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private) { - d->snapping=set; + d->id = id; + d->name = name; + d->isSnappingActive = true; + d->outlineVisible = true; } -bool KisPaintingAssistant::outline() const +bool KisPaintingAssistant::isSnappingActive() const { - return d->outlineVisible; + return d->isSnappingActive; } -void KisPaintingAssistant::setOutline(bool set) +void KisPaintingAssistant::setSnappingActive(bool set) { - d->outlineVisible=set; + d->isSnappingActive = set; } -void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool drawActive) +void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool isSnappingOn) { - int alpha=100; - if (!drawActive) { - alpha=20; + int alpha = d->assistantColor.alpha(); + if (!isSnappingOn) { + alpha = d->assistantColor.alpha() *0.2; } + painter.save(); - QPen pen_a(QColor(0, 0, 0, alpha), 2); + QPen pen_a(QColor(d->assistantColor.red(), d->assistantColor.green(), d->assistantColor.blue(), alpha), 2); pen_a.setCosmetic(true); painter.setPen(pen_a); painter.drawPath(path); - QPen pen_b(QColor(255, 255, 255, alpha), 0.9); - pen_b.setCosmetic(true); - painter.setPen(pen_b); - painter.drawPath(path); painter.restore(); } void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path) { painter.save(); - QPen pen_a(QColor(0, 0, 0, 50), 1); + QPen pen_a(d->assistantColor, 1); pen_a.setStyle(Qt::SolidLine); pen_a.setCosmetic(true); painter.setPen(pen_a); painter.drawPath(path); - QPen pen_b(QColor(255, 255, 255, 50), 1); - pen_b.setStyle(Qt::DotLine); - pen_b.setCosmetic(true); - painter.setPen(pen_b); - painter.drawPath(path); painter.restore(); } void KisPaintingAssistant::initHandles(QList _handles) { Q_ASSERT(d->handles.isEmpty()); d->handles = _handles; Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) { handle->registerAssistant(this); } } KisPaintingAssistant::~KisPaintingAssistant() { Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) { handle->unregisterAssistant(this); } if(!d->sideHandles.isEmpty()) { Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { handle->unregisterAssistant(this); } } delete d; } const QString& KisPaintingAssistant::id() const { return d->id; } const QString& KisPaintingAssistant::name() const { return d->name; } void KisPaintingAssistant::replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with) { Q_ASSERT(d->handles.contains(_handle)); d->handles.replace(d->handles.indexOf(_handle), _with); Q_ASSERT(!d->handles.contains(_handle)); _handle->unregisterAssistant(this); _with->registerAssistant(this); } -void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle) +void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle, HandleType type) { Q_ASSERT(!d->handles.contains(handle)); - d->handles.append(handle); - handle->registerAssistant(this); - handle.data()->setType('H'); -} + if (HandleType::SIDE == type) { + d->sideHandles.append(handle); + } else { + d->handles.append(handle); + } -void KisPaintingAssistant::addSideHandle(KisPaintingAssistantHandleSP handle) -{ - Q_ASSERT(!d->sideHandles.contains(handle)); - d->sideHandles.append(handle); handle->registerAssistant(this); - handle.data()->setType('S'); + handle.data()->setType(type); } + void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache,KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { Q_UNUSED(updateRect); - Q_UNUSED(canvas); Q_UNUSED(previewVisible); - findHandleLocation(); + + findPerspectiveAssistantHandleLocation(); + if (!useCache) { gc.save(); drawCache(gc, converter, assistantVisible); gc.restore(); return; } + const QRect bound = boundingRect(); - if (bound.isEmpty()) return; + if (bound.isEmpty()) { + return; + } + const QTransform transform = converter->documentToWidgetTransform(); const QRect widgetBound = transform.mapRect(bound); const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport()); if (paintRect.isEmpty()) return; QPixmap cached; bool found = QPixmapCache::find(d->cached, &cached); - if (!(found && - d->cachedTransform == transform && - d->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) { + if (!(found && d->cachedTransform == transform && d->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) { + const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound); Q_ASSERT(!cacheRect.isEmpty()); + if (cached.isNull() || cached.size() != cacheRect.size()) { cached = QPixmap(cacheRect.size()); } + cached.fill(Qt::transparent); QPainter painter(&cached); painter.setRenderHint(QPainter::Antialiasing); painter.setWindow(cacheRect); drawCache(painter, converter, assistantVisible); painter.end(); d->cachedTransform = transform; d->cachedRect = cacheRect.translated(-widgetBound.topLeft()); d->cached = QPixmapCache::insert(cached); } + gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->cachedRect.topLeft())); + + + if (canvas) { + d->m_canvas = canvas; + } } void KisPaintingAssistant::uncache() { d->cached = QPixmapCache::Key(); } QRect KisPaintingAssistant::boundingRect() const { QRectF r; Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) { r = r.united(QRectF(*h, QSizeF(1,1))); } return r.adjusted(-2, -2, 2, 2).toAlignedRect(); } +bool KisPaintingAssistant::isAssistantComplete() const +{ + return true; +} + QByteArray KisPaintingAssistant::saveXml(QMap &handleMap) { - QByteArray data; - QXmlStreamWriter xml(&data); - xml.writeStartDocument(); - xml.writeStartElement("assistant"); - xml.writeAttribute("type",d->id); - xml.writeStartElement("handles"); - Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) { - int id = handleMap.size(); - if (!handleMap.contains(handle)){ - handleMap.insert(handle, id); - } - id = handleMap.value(handle); - xml.writeStartElement("handle"); - xml.writeAttribute("id", QString::number(id)); - xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); - xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); - xml.writeEndElement(); - } - xml.writeEndElement(); - xml.writeEndElement(); - xml.writeEndDocument(); - return data; + QByteArray data; + QXmlStreamWriter xml(&data); + xml.writeStartDocument(); + xml.writeStartElement("assistant"); + xml.writeAttribute("type",d->id); + xml.writeAttribute("active", QString::number(d->isSnappingActive)); + xml.writeStartElement("handles"); + Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) { + int id = handleMap.size(); + if (!handleMap.contains(handle)){ + handleMap.insert(handle, id); + } + id = handleMap.value(handle); + xml.writeStartElement("handle"); + xml.writeAttribute("id", QString::number(id)); + xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); + xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); + xml.writeEndElement(); + } + xml.writeEndElement(); + xml.writeEndElement(); + xml.writeEndDocument(); + return data; } void KisPaintingAssistant::loadXml(KoStore* store, QMap &handleMap, QString path) { int id; double x,y ; store->open(path); QByteArray data = store->read(store->size()); QXmlStreamReader xml(data); while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: + if (xml.name() == "assistant") { + QStringRef active = xml.attributes().value("active"); + d->isSnappingActive = (active != "0"); + } + if (xml.name() == "handle") { QString strId = xml.attributes().value("id").toString(), - strX = xml.attributes().value("x").toString(), - strY = xml.attributes().value("y").toString(); + strX = xml.attributes().value("x").toString(), + strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { id = strId.toInt(); x = strX.toDouble(); y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } } - addHandle(handleMap.value(id)); + addHandle(handleMap.value(id), HandleType::NORMAL); } break; default: break; } } store->close(); } void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count) { if (d->id == "ellipse"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "ellipse"); assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "spline"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "spline"); assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "perspective"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "perspective"); assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "vanishing point"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "vanishing point"); assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "infinite ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "infinite ruler"); assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "parallel ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "parallel ruler"); assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "concentric ellipse"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "concentric ellipse"); assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "fisheye-point"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "fisheye-point"); assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "ruler"); assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } } -void KisPaintingAssistant::findHandleLocation() { +void KisPaintingAssistant::findPerspectiveAssistantHandleLocation() { QList hHandlesList; QList vHandlesList; uint vHole = 0,hHole = 0; KisPaintingAssistantHandleSP oppHandle; if (d->handles.size() == 4 && d->id == "perspective") { //get the handle opposite to the first handle oppHandle = oppHandleOne(); //Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively. Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) { hHandlesList.append(handle); hHole = hHandlesList.size() - 1; vHandlesList.append(handle); vHole = vHandlesList.size() - 1; /* sort handles on the basis of X-coordinate */ while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) { hHandlesList.swap(hHole-1, hHole); hHole = hHole - 1; } /* sort handles on the basis of Y-coordinate */ while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) { vHandlesList.swap(vHole-1, vHole); vHole = vHole - 1; } } /* give the handles their respective positions */ if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) { d->topLeft = vHandlesList.at(1); d->topRight= vHandlesList.at(0); } else { d->topLeft = vHandlesList.at(0); d->topRight = vHandlesList.at(1); } if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) { d->bottomLeft = vHandlesList.at(3); d->bottomRight = vHandlesList.at(2); } else { d->bottomLeft= vHandlesList.at(2); d->bottomRight = vHandlesList.at(3); } /* find if the handles that should be opposite are actually oppositely positioned */ if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) || - (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) || - (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) || - (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) ) + (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) || + (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) || + (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) ) {} else { if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) { d->topLeft = hHandlesList.at(1); d->bottomLeft= hHandlesList.at(0); } else { d->topLeft = hHandlesList.at(0); d->bottomLeft = hHandlesList.at(1); } if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) { d->topRight = hHandlesList.at(3); d->bottomRight = hHandlesList.at(2); } else { d->topRight= hHandlesList.at(2); d->bottomRight = hHandlesList.at(3); } } /* Setting the middle handles as needed */ if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) { d->bottomMiddle = new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5); d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, - (d->topLeft.data()->y() + d->topRight.data()->y())*0.5); + (d->topLeft.data()->y() + d->topRight.data()->y())*0.5); d->rightMiddle= new KisPaintingAssistantHandle((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, - (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5); + (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5); d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, - (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5); - addSideHandle(d->rightMiddle.data()); - addSideHandle(d->leftMiddle.data()); - addSideHandle(d->bottomMiddle.data()); - addSideHandle(d->topMiddle.data()); + (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5); + addHandle(d->rightMiddle.data(), HandleType::SIDE); + addHandle(d->leftMiddle.data(), HandleType::SIDE); + addHandle(d->bottomMiddle.data(), HandleType::SIDE); + addHandle(d->topMiddle.data(), HandleType::SIDE); } else { d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, - (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5)); + (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5)); d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, - (d->topLeft.data()->y() + d->topRight.data()->y())*0.5)); + (d->topLeft.data()->y() + d->topRight.data()->y())*0.5)); d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, - (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5)); + (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5)); d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, - (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5)); + (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5)); } } } KisPaintingAssistantHandleSP KisPaintingAssistant::oppHandleOne() { QPointF intersection(0,0); if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) { return d->handles.at(1); } else if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) { return d->handles.at(2); } else { return d->handles.at(3); } } KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() { return d->topLeft; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() const { return d->topLeft; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() { return d->bottomLeft; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() const { return d->bottomLeft; } KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() { return d->topRight; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() const { return d->topRight; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() { return d->bottomRight; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() const { return d->bottomRight; } KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() { return d->topMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() const { return d->topMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() { return d->bottomMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() const { return d->bottomMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() { return d->rightMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() const { return d->rightMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() { return d->leftMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() const { return d->leftMiddle; } const QList& KisPaintingAssistant::handles() const { return d->handles; } QList KisPaintingAssistant::handles() { return d->handles; } const QList& KisPaintingAssistant::sideHandles() const { return d->sideHandles; } QList KisPaintingAssistant::sideHandles() { return d->sideHandles; } + + +bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo) +{ + int m_handleSize = 16; + + QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)); + return handlerect.contains(pointOne); +} + +KisPaintingAssistantHandleSP KisPaintingAssistant::closestCornerHandleFromPoint(QPointF point) +{ + if (!d->m_canvas) { + return 0; + } + + + if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) { + return topLeft(); + } else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) { + return topRight(); + } else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) { + return bottomLeft(); + } else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) { + return bottomRight(); + } + return 0; +} + + +QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const +{ + QPointF documentCoord = d->m_canvas->image()->pixelToDocument(pixelCoords); + return d->m_canvas->viewConverter()->documentToView(documentCoord); +} + +double KisPaintingAssistant::norm2(const QPointF& p) +{ + return p.x() * p.x() + p.y() * p.y(); +} + + + +/* + * KisPaintingAssistantFactory classes +*/ + KisPaintingAssistantFactory::KisPaintingAssistantFactory() { } KisPaintingAssistantFactory::~KisPaintingAssistantFactory() { } KisPaintingAssistantFactoryRegistry::KisPaintingAssistantFactoryRegistry() { } KisPaintingAssistantFactoryRegistry::~KisPaintingAssistantFactoryRegistry() { Q_FOREACH (const QString &id, keys()) { delete get(id); } dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry "; } KisPaintingAssistantFactoryRegistry* KisPaintingAssistantFactoryRegistry::instance() { return s_instance; } diff --git a/libs/ui/kis_painting_assistant.h b/libs/ui/kis_painting_assistant.h index 56b23f2aa2..3b0c0e508f 100644 --- a/libs/ui/kis_painting_assistant.h +++ b/libs/ui/kis_painting_assistant.h @@ -1,174 +1,215 @@ /* * Copyright (c) 2008 Cyrille Berger + * Copyright (c) 2017 Scott Petrovic * * 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_PAINTING_ASSISTANT_H_ #define _KIS_PAINTING_ASSISTANT_H_ #include #include #include #include #include +#include #include #include class QPainter; class QRect; class QRectF; class KoStore; class KisCoordinatesConverter; class KisCanvas2; class QDomDocument; class QDomElement; #include #include class KisPaintingAssistantHandle; typedef KisSharedPtr KisPaintingAssistantHandleSP; class KisPaintingAssistant; class QPainterPath; +enum HandleType { + NORMAL, + SIDE, + CORNER, + VANISHING_POINT, + ANCHOR +}; + + /** * Represent an handle of the assistant, used to edit the parameters * of an assistants. Handles can be shared between assistants. */ class KRITAUI_EXPORT KisPaintingAssistantHandle : public QPointF, public KisShared { friend class KisPaintingAssistant; + public: KisPaintingAssistantHandle(double x, double y); explicit KisPaintingAssistantHandle(QPointF p); KisPaintingAssistantHandle(const KisPaintingAssistantHandle&); ~KisPaintingAssistantHandle(); void mergeWith(KisPaintingAssistantHandleSP); void uncache(); KisPaintingAssistantHandle& operator=(const QPointF&); void setType(char type); char handleType(); + private: void registerAssistant(KisPaintingAssistant*); void unregisterAssistant(KisPaintingAssistant*); bool containsAssistant(KisPaintingAssistant*); + private: struct Private; Private* const d; }; /** * A KisPaintingAssistant is an object that assist the drawing on the canvas. * With this class you can implement virtual equivalent to ruler or compas. */ class KRITAUI_EXPORT KisPaintingAssistant { public: KisPaintingAssistant(const QString& id, const QString& name); virtual ~KisPaintingAssistant(); const QString& id() const; const QString& name() const; - bool snapping() const;//this returns whether or not the snapping is/should be active. - void setSnapping(bool set); - bool outline() const;//this returns whether or not the preview is/should be active. - void setOutline(bool set); + bool isSnappingActive() const; + void setSnappingActive(bool set); + /** * Adjust the position given in parameter. * @param point the coordinates in point in the document reference * @param strokeBegin the coordinates of the beginning of the stroke */ virtual QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) = 0; virtual void endStroke() { } virtual QPointF buttonPosition() const = 0; virtual int numHandles() const = 0; void replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with); - void addHandle(KisPaintingAssistantHandleSP handle); - void addSideHandle(KisPaintingAssistantHandleSP handle); + void addHandle(KisPaintingAssistantHandleSP handle, HandleType type); + + /// grabs the assistant color/opacity specified from the tool options + /// each assistant might have to use this differently, so just save a reference + void setAssistantColor(QColor color); + virtual void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, bool cached = true,KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true); void uncache(); const QList& handles() const; QList handles(); const QList& sideHandles() const; QList sideHandles(); QByteArray saveXml( QMap &handleMap); void loadXml(KoStore *store, QMap &handleMap, QString path); void saveXmlList(QDomDocument& doc, QDomElement& ssistantsElement, int count); - void findHandleLocation(); + void findPerspectiveAssistantHandleLocation(); KisPaintingAssistantHandleSP oppHandleOne(); /** * Get the topLeft, bottomLeft, topRight and BottomRight corners of the assistant + * Some assistants like the perspective grid have custom logic built around certain handles */ const KisPaintingAssistantHandleSP topLeft() const; KisPaintingAssistantHandleSP topLeft(); const KisPaintingAssistantHandleSP topRight() const; KisPaintingAssistantHandleSP topRight(); const KisPaintingAssistantHandleSP bottomLeft() const; KisPaintingAssistantHandleSP bottomLeft(); const KisPaintingAssistantHandleSP bottomRight() const; KisPaintingAssistantHandleSP bottomRight(); const KisPaintingAssistantHandleSP topMiddle() const; KisPaintingAssistantHandleSP topMiddle(); const KisPaintingAssistantHandleSP rightMiddle() const; KisPaintingAssistantHandleSP rightMiddle(); const KisPaintingAssistantHandleSP leftMiddle() const; KisPaintingAssistantHandleSP leftMiddle(); const KisPaintingAssistantHandleSP bottomMiddle() const; KisPaintingAssistantHandleSP bottomMiddle(); + + // calculates whether a point is near one of the corner points of the assistant + // returns: a corner point from the perspective assistant if the given node is close + // only called once in code when calculating the perspective assistant + KisPaintingAssistantHandleSP closestCornerHandleFromPoint(QPointF point); + + // determines if two points are close to each other + // only used by the nodeNearPoint function (perspective grid assistant). + bool areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo); + + /// determines if the assistant has enough handles to be considered created + /// new assistants get in a "creation" phase where they are currently being made on the canvas + /// it will return false if we are in the middle of creating the assistant. + virtual bool isAssistantComplete() const; + public: /** - * This will paint a path using a white and black colors. + * This will render the final output. The drawCache does rendering most of the time so be sure to check that */ - static void drawPath(QPainter& painter, const QPainterPath& path, bool drawActive=true); - static void drawPreview(QPainter& painter, const QPainterPath& path); + void drawPath(QPainter& painter, const QPainterPath& path, bool drawActive=true); + void drawPreview(QPainter& painter, const QPainterPath& path); + static double norm2(const QPointF& p); + protected: virtual QRect boundingRect() const; + + /// performance layer where the graphics can be drawn from a cache instead of generated every render update virtual void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) = 0; + void initHandles(QList _handles); QList m_handles; + + QPointF pixelToView(const QPoint pixelCoords) const; + private: struct Private; Private* const d; }; /** * Allow to create a painting assistant. */ class KRITAUI_EXPORT KisPaintingAssistantFactory { public: KisPaintingAssistantFactory(); virtual ~KisPaintingAssistantFactory(); virtual QString id() const = 0; virtual QString name() const = 0; virtual KisPaintingAssistant* createPaintingAssistant() const = 0; }; class KRITAUI_EXPORT KisPaintingAssistantFactoryRegistry : public KoGenericRegistry { public: KisPaintingAssistantFactoryRegistry(); ~KisPaintingAssistantFactoryRegistry() override; static KisPaintingAssistantFactoryRegistry* instance(); }; #endif diff --git a/libs/ui/kis_painting_assistants_decoration.cpp b/libs/ui/kis_painting_assistants_decoration.cpp index 3215ef4b6c..b47a109ddf 100644 --- a/libs/ui/kis_painting_assistants_decoration.cpp +++ b/libs/ui/kis_painting_assistants_decoration.cpp @@ -1,242 +1,442 @@ /* * Copyright (c) 2009 Cyrille Berger + * Copyright (c) 2017 Scott Petrovic * * 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_painting_assistants_decoration.h" #include #include #include #include #include #include #include "kis_debug.h" #include "KisDocument.h" +#include "kis_canvas2.h" +#include "kis_icon_utils.h" +#include "KisViewManager.h" #include struct KisPaintingAssistantsDecoration::Private { Private() : assistantVisible(false) , outlineVisible(false) , snapOnlyOneAssistant(true) , firstAssistant(0) , aFirstStroke(false) {} bool assistantVisible; bool outlineVisible; bool snapOnlyOneAssistant; KisPaintingAssistantSP firstAssistant; bool aFirstStroke; + QColor m_assistantsColor = QColor(176, 176, 176, 255); // kis_assistant_tool has same default color specified + bool m_isEditingAssistants = false; + bool m_outlineVisible = false; + int m_handleSize = 14; // size of editor handles on assistants + + // move, visibility, delete icons for each assistant. These only display while the assistant tool is active + // these icons will be covered by the kis_paintint_assistant_decoration with things like the perspective assistant + + AssistantEditorData toolData; + + QPixmap m_iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(toolData.deleteIconSize, toolData.deleteIconSize); + QPixmap m_iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(toolData.snapIconSize, toolData.snapIconSize); + QPixmap m_iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(toolData.snapIconSize, toolData.snapIconSize); + QPixmap m_iconMove = KisIconUtils::loadIcon("transform-move").pixmap(toolData.moveIconSize, toolData.moveIconSize); + + KisCanvas2 * m_canvas = 0; }; + + KisPaintingAssistantsDecoration::KisPaintingAssistantsDecoration(QPointer parent) : KisCanvasDecoration("paintingAssistantsDecoration", parent), d(new Private) { setAssistantVisible(true); setOutlineVisible(true); - d->snapOnlyOneAssistant=true;//turn on by default. + d->snapOnlyOneAssistant = true; //turn on by default. } KisPaintingAssistantsDecoration::~KisPaintingAssistantsDecoration() { delete d; } void KisPaintingAssistantsDecoration::addAssistant(KisPaintingAssistantSP assistant) { QList assistants = view()->document()->assistants(); if (assistants.contains(assistant)) return; assistants.append(assistant); view()->document()->setAssistants(assistants); setVisible(!assistants.isEmpty()); emit assistantChanged(); } void KisPaintingAssistantsDecoration::removeAssistant(KisPaintingAssistantSP assistant) { QList assistants = view()->document()->assistants(); KIS_ASSERT_RECOVER_NOOP(assistants.contains(assistant)); if (assistants.removeAll(assistant)) { view()->document()->setAssistants(assistants); setVisible(!assistants.isEmpty()); emit assistantChanged(); } } void KisPaintingAssistantsDecoration::removeAll() { QList assistants = view()->document()->assistants(); assistants.clear(); view()->document()->setAssistants(assistants); setVisible(!assistants.isEmpty()); emit assistantChanged(); } QPointF KisPaintingAssistantsDecoration::adjustPosition(const QPointF& point, const QPointF& strokeBegin) { - QList assistants = view()->document()->assistants(); - if (assistants.empty()) return point; - if (assistants.count() == 1) { - if(assistants.first()->snapping()==true){ - QPointF newpoint = assistants.first()->adjustPosition(point, strokeBegin); + if (assistants().empty()) { + return point; + } + + if (assistants().count() == 1) { + if(assistants().first()->isSnappingActive() == true){ + QPointF newpoint = assistants().first()->adjustPosition(point, strokeBegin); // check for NaN if (newpoint.x() != newpoint.x()) return point; return newpoint; } } QPointF best = point; double distance = DBL_MAX; //the following tries to find the closest point to stroke-begin. It checks all assistants for the closest point// if(!d->snapOnlyOneAssistant){ - Q_FOREACH (KisPaintingAssistantSP assistant, assistants) { - if(assistant->snapping()==true){//this checks if the assistant in question has it's snapping boolean turned on// + Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { + if(assistant->isSnappingActive() == true){//this checks if the assistant in question has it's snapping boolean turned on// QPointF pt = assistant->adjustPosition(point, strokeBegin); if (pt.x() != pt.x()) continue; double dist = qAbs(pt.x() - point.x()) + qAbs(pt.y() - point.y()); if (dist < distance) { best = pt; distance = dist; } } } } else if (d->aFirstStroke==false) { - Q_FOREACH (KisPaintingAssistantSP assistant, assistants) { - if(assistant->snapping()==true){//this checks if the assistant in question has it's snapping boolean turned on// + Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { + if(assistant->isSnappingActive() == true){//this checks if the assistant in question has it's snapping boolean turned on// QPointF pt = assistant->adjustPosition(point, strokeBegin); if (pt.x() != pt.x()) continue; double dist = qAbs(pt.x() - point.x()) + qAbs(pt.y() - point.y()); if (dist < distance) { best = pt; distance = dist; d->firstAssistant = assistant; } } } } else if(d->firstAssistant) { //make sure there's a first assistant to begin with.// best = d->firstAssistant->adjustPosition(point, strokeBegin); } else { d->aFirstStroke=false; } //this is here to be compatible with the movement in the perspective tool. qreal dx = point.x() - strokeBegin.x(), dy = point.y() - strokeBegin.y(); if (dx * dx + dy * dy >= 4.0) { // allow some movement before snapping d->aFirstStroke=true; } return best; } void KisPaintingAssistantsDecoration::endStroke() { - QList assistants = view()->document()->assistants(); + d->aFirstStroke = false; - d->aFirstStroke=false; - Q_FOREACH (KisPaintingAssistantSP assistant, assistants) { + Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { assistant->endStroke(); } } void KisPaintingAssistantsDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter,KisCanvas2* canvas) -{ +{ + if(assistants().length() == 0) { + return; // no assistants to worry about, ok to exit + } + if (!canvas) { dbgFile<<"canvas does not exist in painting assistant decoration, you may have passed arguments incorrectly:"<m_canvas = canvas; } - QList assistants = view()->document()->assistants(); + // the preview functionality for assistants. do not show while editing + if (d->m_isEditingAssistants) { + d->m_outlineVisible = false; + } + else { + d->m_outlineVisible = outlineVisibility(); + } + + Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { - Q_FOREACH (KisPaintingAssistantSP assistant, assistants) { - assistant->drawAssistant(gc, updateRect, converter, true, canvas, assistantVisibility(), outlineVisibility()); + assistant->setAssistantColor(assistantsColor()); + assistant->drawAssistant(gc, updateRect, converter, true, canvas, assistantVisibility(), d->m_outlineVisible); + + if (isEditingAssistants()) { + drawHandles(assistant, gc, converter); + } } + + // draw editor controls on top of all assistant lines (why this code is last) + if (isEditingAssistants()) { + Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { + drawEditorWidget(assistant, gc, converter); + } + } } -//drawPreview// -QList KisPaintingAssistantsDecoration::handles() +void KisPaintingAssistantsDecoration::drawHandles(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter) { - QList assistants = view()->document()->assistants(); + QTransform initialTransform = converter->documentToWidgetTransform(); + Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { + + QPointF transformedHandle = initialTransform.map(*handle); + QRectF ellipse(transformedHandle - QPointF(handleSize() * 0.5, handleSize() * 0.5), QSizeF(handleSize(), handleSize())); + + QPainterPath path; + path.addEllipse(ellipse); + + gc.save(); + gc.setPen(Qt::NoPen); + gc.setBrush(assistantsColor()); + gc.drawPath(path); + gc.restore(); + } + + // some assistants have side handles like the vanishing point assistant + Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) { + QPointF transformedHandle = initialTransform.map(*handle); + QRectF ellipse(transformedHandle - QPointF(handleSize() * 0.5, handleSize() * 0.5), QSizeF(handleSize(), handleSize())); + + QPainterPath path; + path.addEllipse(ellipse); + + gc.save(); + gc.setPen(Qt::NoPen); + gc.setBrush(assistantsColor()); + gc.drawPath(path); + gc.restore(); + } +} + +int KisPaintingAssistantsDecoration::handleSize() +{ + return d->m_handleSize; +} + +void KisPaintingAssistantsDecoration::setHandleSize(int handleSize) +{ + d->m_handleSize = handleSize; +} + +QList KisPaintingAssistantsDecoration::handles() +{ QList hs; - Q_FOREACH (KisPaintingAssistantSP assistant, assistants) { + Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { if (!hs.contains(handle)) { hs.push_back(handle); } } Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) { if (!hs.contains(handle)) { hs.push_back(handle); } } } return hs; } QList KisPaintingAssistantsDecoration::assistants() { QList assistants = view()->document()->assistants(); return assistants; } void KisPaintingAssistantsDecoration::setAssistantVisible(bool set) { d->assistantVisible=set; } void KisPaintingAssistantsDecoration::setOutlineVisible(bool set) { d->outlineVisible=set; } void KisPaintingAssistantsDecoration::setOnlyOneAssistantSnap(bool assistant) { d->snapOnlyOneAssistant = assistant; } bool KisPaintingAssistantsDecoration::assistantVisibility() { return d->assistantVisible; } bool KisPaintingAssistantsDecoration::outlineVisibility() { return d->outlineVisible; } void KisPaintingAssistantsDecoration::uncache() { - QList assistants = view()->document()->assistants(); - - Q_FOREACH (KisPaintingAssistantSP assistant, assistants) { + Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { assistant->uncache(); } } void KisPaintingAssistantsDecoration::toggleAssistantVisible() { setAssistantVisible(!assistantVisibility()); uncache(); } + void KisPaintingAssistantsDecoration::toggleOutlineVisible() { setOutlineVisible(!outlineVisibility()); } + +QColor KisPaintingAssistantsDecoration::assistantsColor() { + return d->m_assistantsColor; +} + +void KisPaintingAssistantsDecoration::setAssistantsColor(QColor color) +{ + d->m_assistantsColor = color; + uncache(); +} + +void KisPaintingAssistantsDecoration::activateAssistantsEditor() +{ + setVisible(true); // this turns on the decorations in general. we leave it on at this point + d->m_isEditingAssistants = true; + uncache(); // updates visuals when editing +} + +void KisPaintingAssistantsDecoration::deactivateAssistantsEditor() +{ + if (!d->m_canvas) { + return; + } + + d->m_isEditingAssistants = false; // some elements are hidden when we aren't editing + uncache(); // updates visuals when not editing +} + +bool KisPaintingAssistantsDecoration::isEditingAssistants() +{ + return d->m_isEditingAssistants; +} + +QPointF KisPaintingAssistantsDecoration::snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers) +{ + if (!d->m_canvas || !d->m_canvas->currentImage()) { + return e->point; + } + + + KoSnapGuide *snapGuide = d->m_canvas->snapGuide(); + QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); + + return pos; +} + +QPointF KisPaintingAssistantsDecoration::snapToGuide(const QPointF& pt, const QPointF &offset) +{ + if (!d->m_canvas) { + return pt; + } + + + KoSnapGuide *snapGuide = d->m_canvas->snapGuide(); + QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); + + return pos; +} + +/* + * functions only used interally in this class + * we potentially could make some of these inline to speed up performance +*/ + +void KisPaintingAssistantsDecoration::drawEditorWidget(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter) +{ + if (!assistant->isAssistantComplete()) { + return; + } + + QTransform initialTransform = converter->documentToWidgetTransform(); + + // We are going to put all of the assistant actions below the bounds of the assistant + // so they are out of the way + // assistant->buttonPosition() gets the center X/Y position point + QPointF actionsPosition = initialTransform.map(assistant->buttonPosition()); + + AssistantEditorData toolData; // shared const data for positioning and sizing + + QPointF iconMovePosition(actionsPosition + toolData.moveIconPosition); + QPointF iconSnapPosition(actionsPosition + toolData.snapIconPosition); + QPointF iconDeletePosition(actionsPosition + toolData.deleteIconPosition); + + // Background container for helpers + QBrush backgroundColor = d->m_canvas->viewManager()->mainWindow()->palette().window(); + QPointF actionsBGRectangle(actionsPosition + QPointF(10, 10)); + + gc.setRenderHint(QPainter::Antialiasing); + + QPainterPath bgPath; + bgPath.addRoundedRect(QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), 110, 40), 6, 6); + QPen stroke(QColor(60, 60, 60, 80), 2); + gc.setPen(stroke); + gc.fillPath(bgPath, backgroundColor); + gc.drawPath(bgPath); + + + // Move Assistant Tool helper + gc.drawPixmap(iconMovePosition, d->m_iconMove); + + // active toggle + if (assistant->isSnappingActive() == true) { + gc.drawPixmap(iconSnapPosition, d->m_iconSnapOn); + } + else { + gc.drawPixmap(iconSnapPosition, d->m_iconSnapOff); + } + + gc.drawPixmap(iconDeletePosition, d->m_iconDelete); + +} diff --git a/libs/ui/kis_painting_assistants_decoration.h b/libs/ui/kis_painting_assistants_decoration.h index 61362b9e92..de1d4cbf2a 100644 --- a/libs/ui/kis_painting_assistants_decoration.h +++ b/libs/ui/kis_painting_assistants_decoration.h @@ -1,75 +1,134 @@ /* * Copyright (c) 2009 Cyrille Berger + * Copyright (c) 2017 Scott Petrovic * * 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_PAINTING_ASSISTANTS_MANAGER_H_ #define _KIS_PAINTING_ASSISTANTS_MANAGER_H_ #include +#include + +#include "KoPointerEvent.h" +#include "KoSnapGuide.h" #include "canvas/kis_canvas_decoration.h" #include "kis_painting_assistant.h" - #include class KisView; class KisPaintingAssistantsDecoration; typedef KisSharedPtr KisPaintingAssistantsDecorationSP; +/// data for editor widget. This is shared between the decoration and assistant tool which needs hit box information +struct AssistantEditorData { + const int moveIconSize = 32; + const int deleteIconSize = 24; + const int snapIconSize = 20; + const QPointF moveIconPosition = QPointF(15, 15); + const QPointF snapIconPosition = QPointF(54, 20); + const QPointF deleteIconPosition = QPointF(83, 18); +}; + /** * This class hold a list of painting assistants. + * In the application flow, each canvas holds one of these classes to manage the assistants + * There is an assistants manager, but that is higher up in the flow and makes sure each view gets one of these + * Since this is off the canvas level, the decoration can be seen across all tools. The contents from here will be in + * front of the kis_assistant_tool, which hold and displays the editor controls. + * + * Many of the events this recieves such as adding and removing assistants comes from kis_assistant_tool */ class KRITAUI_EXPORT KisPaintingAssistantsDecoration : public KisCanvasDecoration { Q_OBJECT public: KisPaintingAssistantsDecoration(QPointer parent); ~KisPaintingAssistantsDecoration() override; void addAssistant(KisPaintingAssistantSP assistant); void removeAssistant(KisPaintingAssistantSP assistant); void removeAll(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin); void endStroke(); QList handles(); QList assistants(); - /*sets whether the main assistant is visible*/ + + /// called when assistant editor is activated + /// right now this happens when the assistants tool is selected + void activateAssistantsEditor(); + + + /// called when assistant editor is deactivated + /// right now this happens when the assistants tool is un-selected + void deactivateAssistantsEditor(); + + /// brings back if we are currently editing assistants or not + /// useful for some assistants (like spline) that draw bezier curves + bool isEditingAssistants(); + + + /// sets whether the main assistant is visible void setAssistantVisible(bool set); - /*sets whether the preview is visible*/ + + /// sets whether the preview is visible void setOutlineVisible(bool set); - /*sets whether we snap to only one assistant*/ + + /// sets whether we snap to only one assistant void setOnlyOneAssistantSnap(bool assistant); - /*returns assistant visibility*/ + + /// returns assistant visibility bool assistantVisibility(); - /*returns preview visibility*/ + + /// returns preview visibility bool outlineVisibility(); - /*uncache all assistants*/ + + /// uncache all assistants void uncache(); + + /// retrieves the assistants color specified in the tool options + /// all assistants will share the same color + QColor assistantsColor(); + + int handleSize(); + void setHandleSize(int handleSize); + + Q_SIGNALS: void assistantChanged(); public Q_SLOTS: + + /// toggles whether the assistant is active or not void toggleAssistantVisible(); + + /// toggles whether there will be a preview of the assistant result when painting void toggleOutlineVisible(); + void setAssistantsColor(QColor color); + QPointF snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers); + QPointF snapToGuide(const QPointF& pt, const QPointF &offset); + protected: void drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter,KisCanvas2* canvas) override; + void drawHandles(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter); + void drawEditorWidget(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter); private: struct Private; Private* const d; }; #endif diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index 5e6991d150..49ad25db1a 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1297 +1,1297 @@ /* * kis_paintop_box.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com) * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2014 Mohit Goyal * * 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_paintop_box.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "widgets/kis_popup_button.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_popup.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_chooser_popup.h" #include "widgets/kis_workspace_chooser.h" #include "widgets/kis_paintop_list_widget.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_widget_chooser.h" #include "tool/kis_tool.h" #include "kis_signals_blocker.h" #include "kis_action_manager.h" #include "kis_highlighted_button.h" typedef KoResourceServerSimpleConstruction > KisPaintOpPresetResourceServer; typedef KoResourceServerAdapter > KisPaintOpPresetResourceServerAdapter; KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->resourceProvider()) , m_optionWidget(0) , m_toolOptionsPopupButton(0) , m_brushEditorPopupButton(0) , m_presetSelectorPopupButton(0) , m_toolOptionsPopup(0) , m_viewManager(view) , m_previousNode(0) , m_currTabletToolID(KoInputDevice::invalid()) , m_presetsEnabled(true) , m_blockUpdate(false) , m_dirtyPresetsEnabled(false) , m_eraserBrushSizeEnabled(false) , m_eraserBrushOpacityEnabled(false) { Q_ASSERT(view != 0); setObjectName(name); KisConfig cfg; m_dirtyPresetsEnabled = cfg.useDirtyPresets(); m_eraserBrushSizeEnabled = cfg.useEraserBrushSize(); m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity(); KAcceleratorManager::setNoAccel(this); setWindowTitle(i18n("Painter's Toolchest")); m_favoriteResourceManager = new KisFavoriteResourceManager(this); KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff"); int iconsize = grp.readEntry("IconSize", 32); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton = new KisPopupButton(this); m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings")); m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize); } m_brushEditorPopupButton = new KisPopupButton(this); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings")); m_brushEditorPopupButton->setFixedSize(iconsize, iconsize); m_presetSelectorPopupButton = new KisPopupButton(this); m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset")); m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize); m_eraseModeButton = new KisHighlightedToolButton(this); m_eraseModeButton->setFixedSize(iconsize, iconsize); m_eraseModeButton->setCheckable(true); m_eraseAction = m_viewManager->actionManager()->createAction("erase_action"); m_eraseModeButton->setDefaultAction(m_eraseAction); m_reloadButton = new QToolButton(this); m_reloadButton->setFixedSize(iconsize, iconsize); m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action"); m_reloadButton->setDefaultAction(m_reloadAction); m_alphaLockButton = new KisHighlightedToolButton(this); m_alphaLockButton->setFixedSize(iconsize, iconsize); m_alphaLockButton->setCheckable(true); KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha"); m_alphaLockButton->setDefaultAction(alphaLockAction); // pen pressure m_disablePressureButton = new KisHighlightedToolButton(this); m_disablePressureButton->setFixedSize(iconsize, iconsize); m_disablePressureButton->setCheckable(true); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); m_disablePressureButton->setDefaultAction(m_disablePressureAction); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); toolbarMenuXMirror->addAction(lockActionX); moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); toolbarMenuXMirror->addAction(moveToCenterActionX); // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); toolbarMenuYMirror->addAction(lockActionY); moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); toolbarMenuYMirror->addAction(moveToCenterActionY); // create horizontal and vertical mirror buttons m_hMirrorButton = new KisHighlightedToolButton(this); int menuPadding = 10; m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_hMirrorButton->setCheckable(true); m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action"); m_hMirrorButton->setDefaultAction(m_hMirrorAction); m_hMirrorButton->setMenu(toolbarMenuXMirror); m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); m_vMirrorButton = new KisHighlightedToolButton(this); m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_vMirrorButton->setCheckable(true); m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action"); m_vMirrorButton->setDefaultAction(m_vMirrorAction); m_vMirrorButton->setMenu(toolbarMenuYMirror); m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); // add connections for horizontal and mirrror buttons connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool))); connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool))); connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX())); connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY())); connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool))); connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool))); const bool sliderLabels = cfg.sliderLabels(); int sliderWidth; if (sliderLabels) { sliderWidth = 150 * logicalDpiX() / 96; } else { sliderWidth = 120 * logicalDpiX() / 96; } for (int i = 0; i < 3; ++i) { m_sliderChooser[i] = new KisWidgetChooser(i + 1); KisDoubleSliderSpinBox* slOpacity; KisDoubleSliderSpinBox* slFlow; KisDoubleSliderSpinBox* slSize; if (sliderLabels) { slOpacity = m_sliderChooser[i]->addWidget("opacity"); slFlow = m_sliderChooser[i]->addWidget("flow"); slSize = m_sliderChooser[i]->addWidget("size"); slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:"))); slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:"))); slSize->setPrefix(QString("%1 ").arg(i18n("Size:"))); } else { slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:")); slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:")); slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:")); } slOpacity->setRange(0.0, 1.0, 2); slOpacity->setValue(1.0); slOpacity->setSingleStep(0.05); slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width())); slOpacity->setFixedHeight(iconsize); slOpacity->setBlockUpdateSignalOnDrag(true); slFlow->setRange(0.0, 1.0, 2); slFlow->setValue(1.0); slFlow->setSingleStep(0.05); slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width())); slFlow->setFixedHeight(iconsize); slFlow->setBlockUpdateSignalOnDrag(true); slSize->setRange(0, cfg.readEntry("maximumBrushSize", 1000), 2); slSize->setValue(100); slSize->setSingleStep(1); slSize->setExponentRatio(3.0); slSize->setSuffix(i18n(" px")); slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width())); slSize->setFixedHeight(iconsize); slSize->setBlockUpdateSignalOnDrag(true); m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1)); } m_cmbCompositeOp = new KisCompositeOpComboBox(); m_cmbCompositeOp->setFixedHeight(iconsize); Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) { m_viewManager->actionManager()->addAction(a->text(), a); } m_workspaceWidget = new KisPopupButton(this); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_workspaceWidget->setToolTip(i18n("Choose workspace")); m_workspaceWidget->setFixedSize(iconsize, iconsize); m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view)); QHBoxLayout* baseLayout = new QHBoxLayout(this); m_paintopWidget = new QWidget(this); baseLayout->addWidget(m_paintopWidget); baseLayout->setSpacing(4); baseLayout->setContentsMargins(0, 0, 0, 0); m_layout = new QHBoxLayout(m_paintopWidget); if (!cfg.toolOptionsInDocker()) { m_layout->addWidget(m_toolOptionsPopupButton); } m_layout->addWidget(m_brushEditorPopupButton); m_layout->addWidget(m_presetSelectorPopupButton); m_layout->setSpacing(4); m_layout->setContentsMargins(0, 0, 0, 0); QWidget* compositeActions = new QWidget(this); QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions); compositeLayout->addWidget(m_cmbCompositeOp); compositeLayout->addWidget(m_eraseModeButton); compositeLayout->addWidget(m_alphaLockButton); compositeLayout->setSpacing(4); compositeLayout->setContentsMargins(0, 0, 0, 0); compositeLayout->addWidget(m_reloadButton); QWidgetAction * action; action = new QWidgetAction(this); view->actionCollection()->addAction("composite_actions", action); action->setText(i18n("Brush composite")); action->setDefaultWidget(compositeActions); QWidget* compositePressure = new QWidget(this); QHBoxLayout* pressureLayout = new QHBoxLayout(compositePressure); pressureLayout->addWidget(m_disablePressureButton); pressureLayout->setSpacing(4); pressureLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); view->actionCollection()->addAction("pressure_action", action); action->setText(i18n("Pressure usage (small button)")); action->setDefaultWidget(compositePressure); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider1", action); view->actionCollection()->addAction("brushslider1", action); action->setDefaultWidget(m_sliderChooser[0]); connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider2", action); view->actionCollection()->addAction("brushslider2", action); action->setDefaultWidget(m_sliderChooser[1]); connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider3", action); view->actionCollection()->addAction("brushslider3", action); action->setDefaultWidget(m_sliderChooser[2]); connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action); view->actionCollection()->addAction("next_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action); view->actionCollection()->addAction("previous_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_preset", action); view->actionCollection()->addAction("previous_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset())); if (!cfg.toolOptionsInDocker()) { action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_tool_options", action); view->actionCollection()->addAction("show_tool_options", action); connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget())); } action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_editor", action); view->actionCollection()->addAction("show_brush_editor", action); connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_presets", action); view->actionCollection()->addAction("show_brush_presets", action); connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget())); QWidget* mirrorActions = new QWidget(this); QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions); mirrorLayout->addWidget(m_hMirrorButton); mirrorLayout->addWidget(m_vMirrorButton); mirrorLayout->setSpacing(4); mirrorLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("mirror_actions", action); action->setDefaultWidget(mirrorActions); view->actionCollection()->addAction("mirror_actions", action); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("workspaces", action); view->actionCollection()->addAction("workspaces", action); action->setDefaultWidget(m_workspaceWidget); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopup = new KisToolOptionsPopup(); m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup); m_toolOptionsPopup->switchDetached(false); } m_savePresetWidget = new KisPresetSaveWidget(this); m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); m_presetsChooserPopup->setMinimumHeight(550); m_presetsChooserPopup->setMinimumWidth(450); m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup); m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); slotNodeChanged(view->activeNode()); // Get all the paintops QList keys = KisPaintOpRegistry::instance()->keys(); QList factoryList; Q_FOREACH (const QString & paintopId, keys) { factoryList.append(KisPaintOpRegistry::instance()->get(paintopId)); } m_presetsPopup->setPaintOpList(factoryList); connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString))); connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset())); connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*))); connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset())); connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool))); connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_resourceProvider , SIGNAL(sigNodeChanged(const KisNodeSP)) , SLOT(slotNodeChanged(const KisNodeSP))); connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int))); connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool))); connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool))); connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool))); m_disablePressureAction->setChecked(true); connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool))); connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool))); connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset())); connect(m_sliderChooser[0]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[1]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[2]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); //Needed to connect canvas to favorite resource manager connect(m_viewManager->resourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode())); connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor))); connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor))); // cold initialization m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor()); m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor()); connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool))); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon())); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); } KisPaintopBox::~KisPaintopBox() { KisConfig cfg; QMapIterator iter(m_tabletToolMap); while (iter.hasNext()) { iter.next(); //qDebug() << "Writing last used preset for" << iter.key().pointer << iter.key().uniqueID << iter.value().preset->name(); if ((iter.key().pointer) == QTabletEvent::Eraser) { cfg.writeEntry(QString("LastEraser_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } else { cfg.writeEntry(QString("LastPreset_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } } // Do not delete the widget, since it it is global to the application, not owned by the view m_presetsPopup->setPaintOpSettingsWidget(0); qDeleteAll(m_paintopOptionWidgets); delete m_favoriteResourceManager; for (int i = 0; i < 3; ++i) { delete m_sliderChooser[i]; } } void KisPaintopBox::restoreResource(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); //qDebug() << "restoreResource" << resource << preset; if (preset) { setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::newOptionWidgets(const QList > &optionWidgetList) { if (m_toolOptionsPopup) { m_toolOptionsPopup->newOptionWidgets(optionWidgetList); } } void KisPaintopBox::resourceSelected(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); if (preset && preset != m_resourceProvider->currentPreset()) { if (!preset->settings()->isLoadable()) return; if (!m_dirtyPresetsEnabled) { KisSignalsBlocker blocker(m_optionWidget); if (!preset->load()) { warnKrita << "failed to load the preset."; } } //qDebug() << "resourceSelected" << resource->name(); setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::setCurrentPaintop(const KoID& paintop) { KisPaintOpPresetSP preset = activePreset(paintop); Q_ASSERT(preset && preset->settings()); //qDebug() << "setCurrentPaintop();" << paintop << preset; setCurrentPaintop(preset); } void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset) { //qDebug() << "setCurrentPaintop(); " << preset->name(); if (preset == m_resourceProvider->currentPreset()) { if (preset == m_tabletToolMap[m_currTabletToolID].preset) { return; } } Q_ASSERT(preset); const KoID& paintop = preset->paintOp(); m_presetConnections.clear(); if (m_resourceProvider->currentPreset()) { m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset()); if (m_optionWidget) { m_optionWidget->hide(); } } if (!m_paintopOptionWidgets.contains(paintop)) m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this); m_optionWidget = m_paintopOptionWidgets[paintop]; KisSignalsBlocker b(m_optionWidget); preset->setOptionsWidget(m_optionWidget); m_optionWidget->setImage(m_viewManager->image()); m_optionWidget->setNode(m_viewManager->activeNode()); m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset())); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP))); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP))); // load the current brush engine icon for the brush editor toolbar button KisPaintOpFactory* paintOp = KisPaintOpRegistry::instance()->get(paintop.id()); QString pixFilename = KoResourcePaths::findResource("kis_images", paintOp->pixmap()); m_brushEditorPopupButton->setIcon(QIcon(pixFilename)); m_presetsPopup->setCurrentPaintOpId(paintop.id()); ////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name(); m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset; m_tabletToolMap[m_currTabletToolID].preset = preset; m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp(); if (m_presetsPopup->currentPaintOpId() != paintop.id()) { // Must change the paintop as the current one is not supported // by the new colorspace. dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace"; } } void KisPaintopBox::slotUpdateOptionsWidgetPopup() { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); KIS_SAFE_ASSERT_RECOVER_RETURN(preset); KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_optionWidget->setConfigurationSafe(preset->settings()); m_presetsPopup->resourceSelected(preset.data()); m_presetsPopup->updateViewSettings(); // the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed // need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation m_optionWidget->setImage(m_viewManager->image()); } KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp) { QString defaultName = paintOp.id() + ".kpp"; QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName); KisPaintOpPresetSP preset = new KisPaintOpPreset(path); if (!preset->load()) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp); } Q_ASSERT(preset); Q_ASSERT(preset->valid()); return preset; } KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp) { if (m_paintOpPresetMap[paintOp] == 0) { m_paintOpPresetMap[paintOp] = defaultPreset(paintOp); } return m_paintOpPresetMap[paintOp]; } void KisPaintopBox::updateCompositeOp(QString compositeOpID) { if (!m_optionWidget) return; KisSignalsBlocker blocker(m_optionWidget); KisNodeSP node = m_resourceProvider->currentNode(); if (node && node->paintDevice()) { if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID)) compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); } if (compositeOpID != m_currCompositeOpID) { m_currCompositeOpID = compositeOpID; } - if (compositeOpID == COMPOSITE_ERASE) { + if (compositeOpID == COMPOSITE_ERASE || m_resourceProvider->eraserMode()) { m_eraseModeButton->setChecked(true); } else { m_eraseModeButton->setChecked(false); } } } void KisPaintopBox::setWidgetState(int flags) { if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) { m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP); m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP); } if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) { m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS); m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS); } for (int i = 0; i < 3; ++i) { if (flags & (ENABLE_OPACITY | DISABLE_OPACITY)) m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY); if (flags & (ENABLE_FLOW | DISABLE_FLOW)) m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW); if (flags & (ENABLE_SIZE | DISABLE_SIZE)) m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE); } } void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value) { for (int i = 0; i < 3; ++i) { KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget(sliderID); KisSignalsBlocker b(slider); slider->setValue(value); } } void KisPaintopBox::slotSetPaintop(const QString& paintOpId) { if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) { KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name()); //qDebug() << "slotsetpaintop" << id; setCurrentPaintop(id); } } void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice) { TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice); //qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId(); m_currTabletToolID = TabletToolID(inputDevice); if (toolData == m_tabletToolMap.end()) { KisConfig cfg; KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false); KisPaintOpPresetSP preset; if (inputDevice.pointer() == QTabletEvent::Eraser) { preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), "Eraser_circle")); } else { preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), "Basic_tip_default")); //if (preset) //qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId(); //else //qDebug() << "no preset fcound for" << inputDevice.uniqueTabletId(); } if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (preset) { //qDebug() << "inputdevicechanged 1" << preset; setCurrentPaintop(preset); } } else { if (toolData->preset) { //qDebug() << "inputdevicechanged 2" << toolData->preset; setCurrentPaintop(toolData->preset); } else { //qDebug() << "inputdevicechanged 3" << toolData->paintOpID; setCurrentPaintop(toolData->paintOpID); } } } void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value) { if (m_viewManager) { sender()->blockSignals(true); KisPaintOpPresetSP preset = m_viewManager->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) { QString compositeOp = preset->settings()->getString("CompositeOp"); updateCompositeOp(compositeOp); resourceSelected(preset.data()); } /** * Update currently selected preset in both the popup widgets */ m_presetsChooserPopup->canvasResourceChanged(preset); m_presetsPopup->currentPresetChanged(preset); if (key == KisCanvasResourceProvider::CurrentCompositeOp) { if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) { updateCompositeOp(m_resourceProvider->currentCompositeOp()); } } if (key == KisCanvasResourceProvider::Size) { setSliderValue("size", m_resourceProvider->size()); } if (key == KisCanvasResourceProvider::Opacity) { setSliderValue("opacity", m_resourceProvider->opacity()); } if (key == KisCanvasResourceProvider::Flow) { setSliderValue("flow", m_resourceProvider->flow()); } if (key == KisCanvasResourceProvider::EraserMode) { m_eraseAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::DisablePressure) { m_disablePressureAction->setChecked(value.toBool()); } sender()->blockSignals(false); } } void KisPaintopBox::slotUpdatePreset() { if (!m_resourceProvider->currentPreset()) return; // block updates of avoid some over updating of the option widget m_blockUpdate = true; setSliderValue("size", m_resourceProvider->size()); { qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity(); m_resourceProvider->setOpacity(opacity); setSliderValue("opacity", opacity); setWidgetState(ENABLE_OPACITY); } { setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow()); setWidgetState(ENABLE_FLOW); } { updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp()); setWidgetState(ENABLE_COMPOSITEOP); } m_blockUpdate = false; } void KisPaintopBox::slotSetupDefaultPreset() { KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp()); preset->setOptionsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); // tell the brush editor that the resource has changed // so it can update everything m_presetsPopup->resourceSelected(preset.data()); } void KisPaintopBox::slotNodeChanged(const KisNodeSP node) { if (m_previousNode.isValid() && m_previousNode->paintDevice()) disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); // Reconnect colorspace change of node if (node && node->paintDevice()) { connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID); m_previousNode = node; slotColorSpaceChanged(node->colorSpace()); } if (m_optionWidget) { m_optionWidget->setNode(node); } } void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace) { m_cmbCompositeOp->validate(colorSpace); } void KisPaintopBox::slotToggleEraseMode(bool checked) { const bool oldEraserMode = m_resourceProvider->eraserMode(); m_resourceProvider->setEraserMode(checked); if (oldEraserMode != checked && m_eraserBrushSizeEnabled) { const qreal currentSize = m_resourceProvider->size(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush size. set the eraser size to the normal brush size if not set if (checked) { settings->setSavedBrushSize(currentSize); if (qFuzzyIsNull(settings->savedEraserSize())) { settings->setSavedEraserSize(currentSize); } } else { settings->setSavedEraserSize(currentSize); if (qFuzzyIsNull(settings->savedBrushSize())) { settings->setSavedBrushSize(currentSize); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize(); m_resourceProvider->setSize(newSize); } if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) { const qreal currentOpacity = m_resourceProvider->opacity(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush opacity. set the eraser opacity to the normal brush opacity if not set if (checked) { settings->setSavedBrushOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedEraserOpacity())) { settings->setSavedEraserOpacity(currentOpacity); } } else { settings->setSavedEraserOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedBrushOpacity())) { settings->setSavedBrushOpacity(currentOpacity); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity(); m_resourceProvider->setOpacity(newOpacity); } } void KisPaintopBox::slotSetCompositeMode(int index) { Q_UNUSED(index); QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id(); m_resourceProvider->setCurrentCompositeOp(compositeOp); } void KisPaintopBox::slotHorizontalMirrorChanged(bool value) { m_resourceProvider->setMirrorHorizontal(value); } void KisPaintopBox::slotVerticalMirrorChanged(bool value) { m_resourceProvider->setMirrorVertical(value); } void KisPaintopBox::sliderChanged(int n) { if (!m_optionWidget) // widget will not exist if the are no documents open return; KisSignalsBlocker blocker(m_optionWidget); qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value(); qreal flow = m_sliderChooser[n]->getWidget("flow")->value(); qreal size = m_sliderChooser[n]->getWidget("size")->value(); setSliderValue("opacity", opacity); setSliderValue("flow" , flow); setSliderValue("size" , size); if (m_presetsEnabled) { // IMPORTANT: set the PaintOp size before setting the other properties // it wont work the other way // TODO: why?! m_resourceProvider->setSize(size); m_resourceProvider->setOpacity(opacity); m_resourceProvider->setFlow(flow); KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings()); propertiesProxy->setProperty("OpacityValue", opacity); propertiesProxy->setProperty("FlowValue", flow); m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data()); } else { m_resourceProvider->setOpacity(opacity); } m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); } void KisPaintopBox::slotSlider1Changed() { sliderChanged(0); } void KisPaintopBox::slotSlider2Changed() { sliderChanged(1); } void KisPaintopBox::slotSlider3Changed() { sliderChanged(2); } void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId) { Q_UNUSED(canvas); Q_UNUSED(toolId); if (!m_viewManager->canvasBase()) return; QString id = KoToolManager::instance()->activeToolId(); KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id)); if (tool) { int flags = tool->flags(); if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) { setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY); } else { setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY); } if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) { setWidgetState(ENABLE_PRESETS); slotUpdatePreset(); m_presetsEnabled = true; } else { setWidgetState(DISABLE_PRESETS); m_presetsEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) { setWidgetState(ENABLE_SIZE | ENABLE_FLOW); } else { setWidgetState(DISABLE_SIZE | DISABLE_FLOW); } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; QVector presets = m_favoriteResourceManager->favoritePresetList(); for (int i=0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == presets[i]->name()) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } //floating message should have least 2 lines, otherwise //preset thumbnail will be too small to distinguish //(because size of image on floating message depends on amount of lines in msg) m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); return; } } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; QVector presets = m_favoriteResourceManager->favoritePresetList(); for(int i = 0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset()->name() == presets[i]->name()) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); return; } } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { //qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset(); setCurrentPaintop(m_resourceProvider->previousPreset()); m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); } } void KisPaintopBox::slotUnsetEraseMode() { m_eraseAction->setChecked(false); } void KisPaintopBox::slotToggleAlphaLockMode(bool checked) { if (checked) { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked")); } else { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked")); } m_resourceProvider->setGlobalAlphaLock(checked); } void KisPaintopBox::slotDisablePressureMode(bool checked) { if (checked) { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } m_resourceProvider->setDisablePressure(checked); } void KisPaintopBox::slotReloadPreset() { KisSignalsBlocker blocker(m_optionWidget); //Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading. KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name()); if (preset) { preset->load(); } } void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { /** * Here we postpone all the settings updates events until thye entire writing * operation will be finished. As soon as it is finished, the updates will be * emitted happily (if there were any). */ KisPaintOpPreset::UpdatedPostponer postponer(preset.data()); m_optionWidget->writeConfigurationSafe(const_cast(preset->settings().data())); } // we should also update the preset strip to update the status of the "dirty" mark m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); // TODO!!!!!!!! //m_presetsPopup->updateViewSettings(); } void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p) { QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value())); if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) { m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous"); } } slotGuiChangedCurrentPreset(); } void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p) { KisSignalsBlocker blocker(m_optionWidget); KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data()); QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); if (preset->settings()->hasProperty(i.key() + "_previous")) { preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous")); preset->settings()->removeProperty(i.key() + "_previous"); } } } //slotUpdatePreset(); } void KisPaintopBox::slotDirtyPresetToggled(bool value) { if (!value) { slotReloadPreset(); m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); m_presetsPopup->updateViewSettings(); } m_dirtyPresetsEnabled = value; KisConfig cfg; cfg.setUseDirtyPresets(m_dirtyPresetsEnabled); } void KisPaintopBox::slotEraserBrushSizeToggled(bool value) { m_eraserBrushSizeEnabled = value; KisConfig cfg; cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled); } void KisPaintopBox::slotEraserBrushOpacityToggled(bool value) { m_eraserBrushOpacityEnabled = value; KisConfig cfg; cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled); } void KisPaintopBox::slotUpdateSelectionIcon() { m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical")); KisConfig cfg; if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) { m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); } m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser")); m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh")); if (m_disablePressureAction->isChecked()) { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } } void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorHorizontalLock(toggleLock); } void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorVerticalLock(toggleLock); } void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) { m_resourceProvider->setMirrorHorizontalHideDecorations(toggled); } void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) { m_resourceProvider->setMirrorVerticalHideDecorations(toggled); } void KisPaintopBox::slotMoveToCenterMirrorX() { m_resourceProvider->mirrorHorizontalMoveCanvasToCenter(); } void KisPaintopBox::slotMoveToCenterMirrorY() { m_resourceProvider->mirrorVerticalMoveCanvasToCenter(); } diff --git a/libs/ui/kis_zoom_manager.cc b/libs/ui/kis_zoom_manager.cc index 54d700ee10..11223bfa47 100644 --- a/libs/ui/kis_zoom_manager.cc +++ b/libs/ui/kis_zoom_manager.cc @@ -1,345 +1,366 @@ /* * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) 2009 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_zoom_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include "KisViewManager.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "kis_image.h" #include "kis_statusbar.h" #include "kis_config.h" #include "krita_utils.h" #include "kis_canvas_resource_provider.h" #include "kis_lod_transform.h" #include "kis_snap_line_strategy.h" +#include "kis_guides_config.h" +#include "kis_guides_manager.h" class KisZoomController : public KoZoomController { public: KisZoomController(KoCanvasController *co, KisCoordinatesConverter *zh, KActionCollection *actionCollection, KoZoomAction::SpecialButtons specialButtons, QObject *parent) : KoZoomController(co, zh, actionCollection, specialButtons, parent), m_converter(zh) { } protected: QSize documentToViewport(const QSizeF &size) override { QRectF docRect(QPointF(), size); return m_converter->documentToWidget(docRect).toRect().size(); } private: KisCoordinatesConverter *m_converter; }; KisZoomManager::KisZoomManager(QPointer view, KoZoomHandler * zoomHandler, KoCanvasController * canvasController) : m_view(view) , m_zoomHandler(zoomHandler) , m_canvasController(canvasController) , m_horizontalRuler(0) , m_verticalRuler(0) , m_zoomAction(0) , m_zoomActionWidget(0) { } KisZoomManager::~KisZoomManager() { if (m_zoomActionWidget && !m_zoomActionWidget->parent()) { delete m_zoomActionWidget; } } void KisZoomManager::setup(KActionCollection * actionCollection) { KisImageWSP image = m_view->image(); if (!image) return; connect(image, SIGNAL(sigSizeChanged(const QPointF &, const QPointF &)), this, SLOT(setMinMaxZoom())); KisCoordinatesConverter *converter = dynamic_cast(m_zoomHandler); m_zoomController = new KisZoomController(m_canvasController, converter, actionCollection, KoZoomAction::AspectMode, this); m_zoomHandler->setZoomMode(KoZoomMode::ZOOM_PIXELS); m_zoomHandler->setZoom(1.0); m_zoomController->setPageSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes())); m_zoomController->setDocumentSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()), true); m_zoomAction = m_zoomController->zoomAction(); setMinMaxZoom(); m_zoomActionWidget = m_zoomAction->createWidget(0); // Put the canvascontroller in a layout so it resizes with us QGridLayout * layout = new QGridLayout(m_view); layout->setSpacing(0); layout->setMargin(0); m_view->setLayout(layout); m_view->document()->setUnit(KoUnit(KoUnit::Pixel)); m_horizontalRuler = new KoRuler(m_view, Qt::Horizontal, m_zoomHandler); m_horizontalRuler->setShowMousePosition(true); m_horizontalRuler->createGuideToolConnection(m_view->canvasBase()); m_horizontalRuler->setVisible(false); // this prevents the rulers from flashing on to off when a new document is created m_verticalRuler = new KoRuler(m_view, Qt::Vertical, m_zoomHandler); m_verticalRuler->setShowMousePosition(true); m_verticalRuler->createGuideToolConnection(m_view->canvasBase()); m_verticalRuler->setVisible(false); + QAction *rulerAction = actionCollection->action("ruler_pixel_multiple2"); + if (m_view->document()->guidesConfig().rulersMultiple2()) { + m_horizontalRuler->setUnitPixelMultiple2(true); + m_verticalRuler->setUnitPixelMultiple2(true); + } QList unitActions = m_view->createChangeUnitActions(true); + unitActions.append(rulerAction); m_horizontalRuler->setPopupActionList(unitActions); m_verticalRuler->setPopupActionList(unitActions); connect(m_view->document(), SIGNAL(unitChanged(const KoUnit&)), SLOT(applyRulersUnit(const KoUnit&))); + connect(rulerAction, SIGNAL(toggled(bool)), SLOT(setRulersPixelMultiple2(bool))); layout->addWidget(m_horizontalRuler, 0, 1); layout->addWidget(m_verticalRuler, 1, 0); layout->addWidget(static_cast(m_canvasController), 1, 1); connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(pageOffsetChanged())); connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetYChanged(int)), this, SLOT(pageOffsetChanged())); connect(m_zoomController, SIGNAL(zoomChanged(KoZoomMode::Mode, qreal)), this, SLOT(slotZoomChanged(KoZoomMode::Mode, qreal))); connect(m_zoomController, SIGNAL(aspectModeChanged(bool)), this, SLOT(changeAspectMode(bool))); applyRulersUnit(m_view->document()->unit()); } void KisZoomManager::updateImageBoundsSnapping() { const QRectF docRect = m_view->canvasBase()->coordinatesConverter()->imageRectInDocumentPixels(); const QPointF docCenter = docRect.center(); KoSnapGuide *snapGuide = m_view->canvasBase()->snapGuide(); { KisSnapLineStrategy *boundsSnap = new KisSnapLineStrategy(KoSnapGuide::DocumentBoundsSnapping); boundsSnap->addLine(Qt::Horizontal, docRect.y()); boundsSnap->addLine(Qt::Horizontal, docRect.bottom()); boundsSnap->addLine(Qt::Vertical, docRect.x()); boundsSnap->addLine(Qt::Vertical, docRect.right()); snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, boundsSnap); } { KisSnapLineStrategy *centerSnap = new KisSnapLineStrategy(KoSnapGuide::DocumentCenterSnapping); centerSnap->addLine(Qt::Horizontal, docCenter.y()); centerSnap->addLine(Qt::Vertical, docCenter.x()); snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentCenterSnapping, centerSnap); } } void KisZoomManager::updateMouseTrackingConnections() { bool value = m_horizontalRuler->isVisible() && m_verticalRuler->isVisible() && m_horizontalRuler->showMousePosition() && m_verticalRuler->showMousePosition(); m_mouseTrackingConnections.clear(); if (value) { connect(m_canvasController->proxyObject, SIGNAL(canvasMousePositionChanged(const QPoint &)), SLOT(mousePositionChanged(const QPoint &))); } } KoRuler* KisZoomManager::horizontalRuler() const { return m_horizontalRuler; } KoRuler* KisZoomManager::verticalRuler() const { return m_verticalRuler; } qreal KisZoomManager::zoom() const { qreal zoomX; qreal zoomY; m_zoomHandler->zoom(&zoomX, &zoomY); qDebug() << zoomX << zoomY; return zoomX; } void KisZoomManager::mousePositionChanged(const QPoint &viewPos) { QPoint pt = viewPos - m_rulersOffset; m_horizontalRuler->updateMouseCoordinate(pt.x()); m_verticalRuler->updateMouseCoordinate(pt.y()); } void KisZoomManager::setShowRulers(bool show) { m_horizontalRuler->setVisible(show); m_verticalRuler->setVisible(show); updateMouseTrackingConnections(); } void KisZoomManager::setRulersTrackMouse(bool value) { m_horizontalRuler->setShowMousePosition(value); m_verticalRuler->setShowMousePosition(value); updateMouseTrackingConnections(); } void KisZoomManager::applyRulersUnit(const KoUnit &baseUnit) { if (m_view && m_view->image()) { m_horizontalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->xRes())); m_verticalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->yRes())); } + if (m_view->viewManager()) { + m_view->viewManager()->guidesManager()->setUnitType(baseUnit.type()); + } +} + +void KisZoomManager::setRulersPixelMultiple2(bool enabled) +{ + m_horizontalRuler->setUnitPixelMultiple2(enabled); + m_verticalRuler->setUnitPixelMultiple2(enabled); + if (m_view->viewManager()) { + m_view->viewManager()->guidesManager()->setRulersMultiple2(enabled); + } } void KisZoomManager::setMinMaxZoom() { KisImageWSP image = m_view->image(); if (!image) return; QSize imageSize = image->size(); qreal minDimension = qMin(imageSize.width(), imageSize.height()); qreal minZoom = qMin(100.0 / minDimension, 0.1); m_zoomAction->setMinimumZoom(minZoom); m_zoomAction->setMaximumZoom(90.0); } void KisZoomManager::updateGUI() { QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels(); QSize documentSize = m_view->canvasBase()->viewConverter()->viewToDocument(widgetRect).toAlignedRect().size(); m_horizontalRuler->setRulerLength(documentSize.width()); m_verticalRuler->setRulerLength(documentSize.height()); applyRulersUnit(m_horizontalRuler->unit()); } QWidget *KisZoomManager::zoomActionWidget() const { return m_zoomActionWidget; } void KisZoomManager::slotZoomChanged(KoZoomMode::Mode mode, qreal zoom) { Q_UNUSED(mode); Q_UNUSED(zoom); m_view->canvasBase()->notifyZoomChanged(); qreal humanZoom = zoom * 100.0; // XXX: KOMVC -- this is very irritating in MDI mode if (m_view->viewManager()) { m_view->viewManager()-> showFloatingMessage( i18nc("floating message about zoom", "Zoom: %1 %", KritaUtils::prettyFormatReal(humanZoom)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } const qreal effectiveZoom = m_view->canvasBase()->coordinatesConverter()->effectiveZoom(); m_view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::EffectiveZoom, effectiveZoom); } void KisZoomManager::slotScrollAreaSizeChanged() { pageOffsetChanged(); updateGUI(); } void KisZoomManager::changeAspectMode(bool aspectMode) { KisImageWSP image = m_view->image(); KoZoomMode::Mode newMode = KoZoomMode::ZOOM_CONSTANT; qreal newZoom = m_zoomHandler->zoom(); qreal resolutionX = aspectMode ? image->xRes() : POINT_TO_INCH(static_cast(KoDpi::dpiX())); qreal resolutionY = aspectMode ? image->yRes() : POINT_TO_INCH(static_cast(KoDpi::dpiY())); m_zoomController->setZoom(newMode, newZoom, resolutionX, resolutionY); m_view->canvasBase()->notifyZoomChanged(); } void KisZoomManager::pageOffsetChanged() { QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels(); m_rulersOffset = widgetRect.topLeft().toPoint(); m_horizontalRuler->setOffset(m_rulersOffset.x()); m_verticalRuler->setOffset(m_rulersOffset.y()); } void KisZoomManager::zoomTo100() { m_zoomController->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); m_view->canvasBase()->notifyZoomChanged(); } diff --git a/libs/ui/kis_zoom_manager.h b/libs/ui/kis_zoom_manager.h index f48e37a153..b29681031f 100644 --- a/libs/ui/kis_zoom_manager.h +++ b/libs/ui/kis_zoom_manager.h @@ -1,105 +1,106 @@ /* * Copyright (C) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ZOOM_MANAGER #define KIS_ZOOM_MANAGER #include #include #include #include #include #include #include #include "kis_signal_auto_connection.h" #include "KisView.h" class KoZoomHandler; class KoZoomAction; class KoRuler; class KoUnit; class KoCanvasController; class QPoint; #include "kritaui_export.h" /** * The zoom manager handles all user actions related to zooming * and unzooming. The actual computation of zoom levels and things * are the job of KoZoomHandler or its descendants */ class KRITAUI_EXPORT KisZoomManager : public QObject { Q_OBJECT public: KisZoomManager(QPointer view, KoZoomHandler*, KoCanvasController *); ~KisZoomManager() override; void setup(KActionCollection * actionCollection); void updateGUI(); KoZoomController * zoomController() const { return m_zoomController; } void updateImageBoundsSnapping(); QWidget *zoomActionWidget() const; KoRuler *horizontalRuler() const; KoRuler *verticalRuler() const; qreal zoom() const; public Q_SLOTS: void slotZoomChanged(KoZoomMode::Mode mode, qreal zoom); void slotScrollAreaSizeChanged(); void setShowRulers(bool show); void setRulersTrackMouse(bool value); void mousePositionChanged(const QPoint &viewPos); void changeAspectMode(bool aspectMode); void pageOffsetChanged(); void zoomTo100(); void applyRulersUnit(const KoUnit &baseUnit); void setMinMaxZoom(); + void setRulersPixelMultiple2(bool enabled); private: void updateMouseTrackingConnections(); private: QPointer m_view; KoZoomHandler * m_zoomHandler; KoCanvasController *m_canvasController; KoZoomController *m_zoomController; KoRuler * m_horizontalRuler; KoRuler * m_verticalRuler; KoZoomAction * m_zoomAction; QPointer m_zoomActionWidget; QPoint m_rulersOffset; KisSignalAutoConnectionsStore m_mouseTrackingConnections; }; #endif diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc index 483e0033bf..eb89774cee 100644 --- a/libs/ui/tool/kis_tool.cc +++ b/libs/ui/tool/kis_tool.cc @@ -1,697 +1,702 @@ /* * Copyright (c) 2006, 2010 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl_canvas2.h" #include "kis_canvas_resource_provider.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "filter/kis_filter_configuration.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include #include #include "kis_resources_snapshot.h" #include #include "kis_action_registry.h" #include "kis_tool_utils.h" struct Q_DECL_HIDDEN KisTool::Private { QCursor cursor; // the cursor that should be shown on tool activation. // From the canvas resources KoPattern* currentPattern{0}; KoAbstractGradient* currentGradient{0}; KoColor currentFgColor; KoColor currentBgColor; float currentExposure{1.0}; KisFilterConfigurationSP currentGenerator; QWidget* optionWidget{0}; ToolMode m_mode{HOVER_MODE}; bool m_isActive{false}; }; KisTool::KisTool(KoCanvasBase * canvas, const QCursor & cursor) : KoToolBase(canvas) , d(new Private) { d->cursor = cursor; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle())); connect(this, SIGNAL(isActiveChanged()), SLOT(resetCursorStyle())); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); if (!collection->action("toggle_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("toggle_fg_bg", collection); collection->addAction("toggle_fg_bg", toggleFgBg); } if (!collection->action("reset_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("reset_fg_bg", collection); collection->addAction("reset_fg_bg", toggleFgBg); } addAction("toggle_fg_bg", dynamic_cast(collection->action("toggle_fg_bg"))); addAction("reset_fg_bg", dynamic_cast(collection->action("reset_fg_bg"))); } KisTool::~KisTool() { delete d; } void KisTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); resetCursorStyle(); if (!canvas()) return; if (!canvas()->resourceManager()) return; d->currentFgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value(); d->currentBgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::BackgroundColor).value(); if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentPattern)) { d->currentPattern = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPattern).value(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGradient)) { d->currentGradient = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGradient).value(); } KisPaintOpPresetSP preset = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && preset->settings()) { preset->settings()->activate(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::HdrExposure)) { d->currentExposure = static_cast(canvas()->resourceManager()->resource(KisCanvasResourceProvider::HdrExposure).toDouble()); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration)) { d->currentGenerator = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); } connect(action("toggle_fg_bg"), SIGNAL(triggered()), SLOT(slotToggleFgBg()), Qt::UniqueConnection); connect(action("reset_fg_bg"), SIGNAL(triggered()), SLOT(slotResetFgBg()), Qt::UniqueConnection); connect(image(), SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection); d->m_isActive = true; emit isActiveChanged(); } void KisTool::deactivate() { bool result = true; result &= disconnect(image().data(), SIGNAL(sigUndoDuringStrokeRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeCancellationRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeEndRequested()), this, 0); result &= disconnect(action("toggle_fg_bg"), 0, this, 0); result &= disconnect(action("reset_fg_bg"), 0, this, 0); if (!result) { warnKrita << "WARNING: KisTool::deactivate() failed to disconnect" << "some signal connections. Your actions might be executed twice!"; } d->m_isActive = false; emit isActiveChanged(); KoToolBase::deactivate(); } void KisTool::canvasResourceChanged(int key, const QVariant & v) { + QString formattedBrushName; + if (key == KisCanvasResourceProvider::CurrentPaintOpPreset) { + QString formattedBrushName = v.value()->name().replace("_", " "); + } + switch (key) { case(KoCanvasResourceManager::ForegroundColor): d->currentFgColor = v.value(); break; case(KoCanvasResourceManager::BackgroundColor): d->currentBgColor = v.value(); break; case(KisCanvasResourceProvider::CurrentPattern): d->currentPattern = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentGradient): d->currentGradient = static_cast(v.value()); break; case(KisCanvasResourceProvider::HdrExposure): d->currentExposure = static_cast(v.toDouble()); break; case(KisCanvasResourceProvider::CurrentGeneratorConfiguration): d->currentGenerator = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentPaintOpPreset): - emit statusTextChanged(v.value()->name()); + emit statusTextChanged(formattedBrushName); break; case(KisCanvasResourceProvider::CurrentKritaNode): resetCursorStyle(); break; default: break; // Do nothing }; } void KisTool::updateSettingsViews() { } QPointF KisTool::widgetCenterInWidgetPixels() { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter(); return converter->flakeToWidget(converter->flakeCenterPoint()); } QPointF KisTool::convertDocumentToWidget(const QPointF& pt) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->documentToWidget(pt); } QPointF KisTool::convertToPixelCoord(KoPointerEvent *e) { if (!image()) return e->point; return image()->documentToPixel(e->point); } QPointF KisTool::convertToPixelCoord(const QPointF& pt) { if (!image()) return pt; return image()->documentToPixel(pt); } QPointF KisTool::convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!image()) return e->point; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); return image()->documentToPixel(pos); } QPointF KisTool::convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset) { if (!image()) return pt; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return image()->documentToPixel(pos); } QPoint KisTool::convertToImagePixelCoordFloored(KoPointerEvent *e) { if (!image()) return e->point.toPoint(); return image()->documentToImagePixelFloored(e->point); } QPointF KisTool::viewToPixel(const QPointF &viewCoord) const { if (!image()) return viewCoord; return image()->documentToPixel(canvas()->viewConverter()->viewToDocument(viewCoord)); } QRectF KisTool::convertToPt(const QRectF &rect) { if (!image()) return rect; QRectF r; //We add 1 in the following to the extreme coords because a pixel always has size r.setCoords(int(rect.left()) / image()->xRes(), int(rect.top()) / image()->yRes(), int(rect.right()) / image()->xRes(), int( rect.bottom()) / image()->yRes()); return r; } QPointF KisTool::pixelToView(const QPoint &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QPointF KisTool::pixelToView(const QPointF &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QRectF KisTool::pixelToView(const QRectF &pixelRect) const { if (!image()) return pixelRect; QPointF topLeft = pixelToView(pixelRect.topLeft()); QPointF bottomRight = pixelToView(pixelRect.bottomRight()); return QRectF(topLeft, bottomRight); } QPainterPath KisTool::pixelToView(const QPainterPath &pixelPolygon) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPolygon); } QPolygonF KisTool::pixelToView(const QPolygonF &pixelPath) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPath); } void KisTool::updateCanvasPixelRect(const QRectF &pixelRect) { canvas()->updateCanvas(convertToPt(pixelRect)); } void KisTool::updateCanvasViewRect(const QRectF &viewRect) { canvas()->updateCanvas(canvas()->viewConverter()->viewToDocument(viewRect)); } KisImageWSP KisTool::image() const { // For now, krita tools only work in krita, not for a krita shape. Krita shapes are for 2.1 KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (kisCanvas) { return kisCanvas->currentImage(); } return 0; } QCursor KisTool::cursor() const { return d->cursor; } void KisTool::notifyModified() const { if (image()) { image()->setModified(); } } KoPattern * KisTool::currentPattern() { return d->currentPattern; } KoAbstractGradient * KisTool::currentGradient() { return d->currentGradient; } KisPaintOpPresetSP KisTool::currentPaintOpPreset() { return canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); } KisNodeSP KisTool::currentNode() const { KisNodeSP node = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); return node; } KisNodeList KisTool::selectedNodes() const { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->nodeManager()->selectedNodes(); } KoColor KisTool::currentFgColor() { return d->currentFgColor; } KoColor KisTool::currentBgColor() { return d->currentBgColor; } KisImageWSP KisTool::currentImage() { return image(); } KisFilterConfigurationSP KisTool::currentGenerator() { return d->currentGenerator; } void KisTool::setMode(ToolMode mode) { d->m_mode = mode; } KisTool::ToolMode KisTool::mode() const { return d->m_mode; } void KisTool::setCursor(const QCursor &cursor) { d->cursor = cursor; } KisTool::AlternateAction KisTool::actionToAlternateAction(ToolAction action) { KIS_ASSERT_RECOVER_RETURN_VALUE(action != Primary, Secondary); return (AlternateAction)action; } void KisTool::activatePrimaryAction() { resetCursorStyle(); } void KisTool::deactivatePrimaryAction() { resetCursorStyle(); } void KisTool::beginPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::beginPrimaryDoubleClickAction(KoPointerEvent *event) { beginPrimaryAction(event); } void KisTool::continuePrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } bool KisTool::primaryActionSupportsHiResEvents() const { return false; } void KisTool::activateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::deactivateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action) { beginAlternateAction(event, action); } void KisTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseTripleClickEvent(KoPointerEvent *event) { mouseDoubleClickEvent(event); } void KisTool::mousePressEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseMoveEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::deleteSelection() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); if (!blockUntilOperationsFinished()) { return; } if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) { KoToolBase::deleteSelection(); } } void KisTool::setupPaintAction(KisRecordedPaintAction* action) { action->setPaintColor(currentFgColor()); action->setBackgroundColor(currentBgColor()); } QWidget* KisTool::createOptionWidget() { d->optionWidget = new QLabel(i18n("No options")); d->optionWidget->setObjectName("SpecialSpacer"); return d->optionWidget; } #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #define PROGRAM_VERTEX_ATTRIBUTE 0 void KisTool::paintToolOutline(QPainter* painter, const QPainterPath &path) { KisOpenGLCanvas2 *canvasWidget = dynamic_cast(canvas()->canvasWidget()); if (canvasWidget) { painter->beginNativePainting(); canvasWidget->paintToolOutline(path); painter->endNativePainting(); } else { painter->save(); painter->setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter->setPen(QColor(128, 255, 128)); painter->drawPath(path); painter->restore(); } } void KisTool::resetCursorStyle() { useCursor(d->cursor); } bool KisTool::overrideCursorIfNotEditable() { // override cursor for canvas iff this tool is active // and we can't paint on the active layer if (isActive()) { KisNodeSP node = currentNode(); if (node && !node->isEditable()) { canvas()->setCursor(Qt::ForbiddenCursor); return true; } } return false; } bool KisTool::blockUntilOperationsFinished() { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->blockUntilOperationsFinished(image()); } void KisTool::blockUntilOperationsFinishedForced() { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->blockUntilOperationsFinishedForced(image()); } bool KisTool::isActive() const { return d->m_isActive; } void KisTool::slotToggleFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); KoColor newFg = resourceManager->backgroundColor(); KoColor newBg = resourceManager->foregroundColor(); /** * NOTE: Some of color selectors do not differentiate foreground * and background colors, so if one wants them to end up * being set up to foreground color, it should be set the * last. */ resourceManager->setBackgroundColor(newBg); resourceManager->setForegroundColor(newFg); } void KisTool::slotResetFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); // see a comment in slotToggleFgBg() resourceManager->setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8())); resourceManager->setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8())); } bool KisTool::nodeEditable() { KisNodeSP node = currentNode(); if (!node) { return false; } bool nodeEditable = node->isEditable(); if (!nodeEditable) { KisCanvas2 * kiscanvas = static_cast(canvas()); QString message; if (!node->visible() && node->userLocked()) { message = i18n("Layer is locked and invisible."); } else if (node->userLocked()) { message = i18n("Layer is locked."); } else if(!node->visible()) { message = i18n("Layer is invisible."); } else { message = i18n("Group not editable."); } kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked")); } return nodeEditable; } bool KisTool::selectionEditable() { KisCanvas2 * kisCanvas = static_cast(canvas()); KisViewManager * view = kisCanvas->viewManager(); bool editable = view->selectionEditable(); if (!editable) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage(i18n("Local selection is locked."), KisIconUtils::loadIcon("object-locked")); } return editable; } void KisTool::listenToModifiers(bool listen) { Q_UNUSED(listen); } bool KisTool::listeningToModifiers() { return false; } diff --git a/libs/ui/tool/kis_tool_paint.cc b/libs/ui/tool/kis_tool_paint.cc index 43f0902cde..7f5009ebcc 100644 --- a/libs/ui/tool/kis_tool_paint.cc +++ b/libs/ui/tool/kis_tool_paint.cc @@ -1,793 +1,797 @@ /* * Copyright (c) 2003-2009 Boudewijn Rempt * Copyright (c) 2015 Moritz Molch * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_paint.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_slider_spin_box.h" #include "kis_canvas_resource_provider.h" #include #include "kis_tool_utils.h" #include #include #include #include #include "strokes/kis_color_picker_stroke_strategy.h" #include KisToolPaint::KisToolPaint(KoCanvasBase * canvas, const QCursor & cursor) : KisTool(canvas, cursor), m_showColorPreview(false), m_colorPreviewShowComparePlate(false), m_colorPickerDelayTimer(), m_isOutlineEnabled(true) { m_specialHoverModifier = false; m_optionsWidgetLayout = 0; m_opacity = OPACITY_OPAQUE_U8; updateTabletPressureSamples(); m_supportOutline = false; { int maxSize = KisConfig().readEntry("maximumBrushSize", 1000); int brushSize = 1; do { m_standardBrushSizes.push_back(brushSize); int increment = qMax(1, int(std::ceil(qreal(brushSize) / 15))); brushSize += increment; } while (brushSize < maxSize); m_standardBrushSizes.push_back(maxSize); } KisCanvas2 * kiscanvas = dynamic_cast(canvas); KisActionManager *actionManager = kiscanvas->viewManager()->actionManager(); // XXX: Perhaps a better place for these? if (!actionManager->actionByName("increase_brush_size")) { KisAction *increaseBrushSize = new KisAction(i18n("Increase Brush Size")); increaseBrushSize->setShortcut(Qt::Key_BracketRight); actionManager->addAction("increase_brush_size", increaseBrushSize); } if (!actionManager->actionByName("decrease_brush_size")) { KisAction *decreaseBrushSize = new KisAction(i18n("Decrease Brush Size")); decreaseBrushSize->setShortcut(Qt::Key_BracketLeft); actionManager->addAction("decrease_brush_size", decreaseBrushSize); } addAction("increase_brush_size", dynamic_cast(actionManager->actionByName("increase_brush_size"))); addAction("decrease_brush_size", dynamic_cast(actionManager->actionByName("decrease_brush_size"))); if (kiscanvas && kiscanvas->viewManager()) { connect(this, SIGNAL(sigPaintingFinished()), kiscanvas->viewManager()->resourceProvider(), SLOT(slotPainting())); } m_colorPickerDelayTimer.setSingleShot(true); connect(&m_colorPickerDelayTimer, SIGNAL(timeout()), this, SLOT(activatePickColorDelayed())); using namespace std::placeholders; // For _1 placeholder std::function callback = std::bind(&KisToolPaint::addPickerJob, this, _1); m_colorPickingCompressor.reset( new PickingCompressor(100, callback, KisSignalCompressor::FIRST_ACTIVE)); } KisToolPaint::~KisToolPaint() { } int KisToolPaint::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP; } void KisToolPaint::canvasResourceChanged(int key, const QVariant& v) { KisTool::canvasResourceChanged(key, v); switch(key) { case(KisCanvasResourceProvider::Opacity): setOpacity(v.toDouble()); break; default: //nothing break; } connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle()), Qt::UniqueConnection); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateTabletPressureSamples()), Qt::UniqueConnection); } void KisToolPaint::activate(ToolActivation toolActivation, const QSet &shapes) { - if (currentPaintOpPreset()) emit statusTextChanged(currentPaintOpPreset()->name()); + if (currentPaintOpPreset()) { + QString formattedBrushName = currentPaintOpPreset()->name().replace("_", " "); + emit statusTextChanged(formattedBrushName); + } + KisTool::activate(toolActivation, shapes); connect(action("increase_brush_size"), SIGNAL(triggered()), SLOT(increaseBrushSize()), Qt::UniqueConnection); connect(action("decrease_brush_size"), SIGNAL(triggered()), SLOT(decreaseBrushSize()), Qt::UniqueConnection); KisCanvasResourceProvider *provider = qobject_cast(canvas())->viewManager()->resourceProvider(); m_oldOpacity = provider->opacity(); provider->setOpacity(m_localOpacity); } void KisToolPaint::deactivate() { disconnect(action("increase_brush_size"), 0, this, 0); disconnect(action("decrease_brush_size"), 0, this, 0); KisCanvasResourceProvider *provider = qobject_cast(canvas())->viewManager()->resourceProvider(); m_localOpacity = provider->opacity(); provider->setOpacity(m_oldOpacity); KisTool::deactivate(); } QPainterPath KisToolPaint::tryFixBrushOutline(const QPainterPath &originalOutline) { KisConfig cfg; if (cfg.newOutlineStyle() == OUTLINE_NONE) return originalOutline; const qreal minThresholdSize = cfg.outlineSizeMinimum(); /** * If the brush outline is bigger than the canvas itself (which * would make it invisible for a user in most of the cases) just * add a cross in the center of it */ QSize widgetSize = canvas()->canvasWidget()->size(); const int maxThresholdSum = widgetSize.width() + widgetSize.height(); QPainterPath outline = originalOutline; QRectF boundingRect = outline.boundingRect(); const qreal sum = boundingRect.width() + boundingRect.height(); QPointF center = boundingRect.center(); if (sum > maxThresholdSum) { const int hairOffset = 7; outline.moveTo(center.x(), center.y() - hairOffset); outline.lineTo(center.x(), center.y() + hairOffset); outline.moveTo(center.x() - hairOffset, center.y()); outline.lineTo(center.x() + hairOffset, center.y()); } else if (sum < minThresholdSize && !outline.isEmpty()) { outline = QPainterPath(); outline.addEllipse(center, 0.5 * minThresholdSize, 0.5 * minThresholdSize); } return outline; } void KisToolPaint::paint(QPainter &gc, const KoViewConverter &converter) { Q_UNUSED(converter); QPainterPath path = tryFixBrushOutline(pixelToView(m_currentOutline)); paintToolOutline(&gc, path); if (m_showColorPreview) { QRectF viewRect = converter.documentToView(m_oldColorPreviewRect); gc.fillRect(viewRect, m_colorPreviewCurrentColor); if (m_colorPreviewShowComparePlate) { QRectF baseColorRect = viewRect.translated(viewRect.width(), 0); gc.fillRect(baseColorRect, m_colorPreviewBaseColor); } } } void KisToolPaint::setMode(ToolMode mode) { if(this->mode() == KisTool::PAINT_MODE && mode != KisTool::PAINT_MODE) { // Let's add history information about recently used colors emit sigPaintingFinished(); } KisTool::setMode(mode); } void KisToolPaint::activatePickColor(AlternateAction action) { m_showColorPreview = true; requestUpdateOutline(m_outlineDocPoint, 0); int resource = colorPreviewResourceId(action); KoColor color = canvas()->resourceManager()->koColorResource(resource); KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); m_colorPreviewCurrentColor = kisCanvas->displayColorConverter()->toQColor(color); if (!m_colorPreviewBaseColor.isValid()) { m_colorPreviewBaseColor = m_colorPreviewCurrentColor; } } void KisToolPaint::deactivatePickColor(AlternateAction action) { Q_UNUSED(action); m_showColorPreview = false; m_oldColorPreviewRect = QRect(); m_oldColorPreviewUpdateRect = QRect(); m_colorPreviewCurrentColor = QColor(); } void KisToolPaint::pickColorWasOverridden() { m_colorPreviewShowComparePlate = false; m_colorPreviewBaseColor = QColor(); } void KisToolPaint::activateAlternateAction(AlternateAction action) { switch (action) { case PickFgNode: case PickBgNode: case PickFgImage: case PickBgImage: delayedAction = action; m_colorPickerDelayTimer.start(100); default: pickColorWasOverridden(); KisTool::activateAlternateAction(action); }; } void KisToolPaint::activatePickColorDelayed() { switch (delayedAction) { case PickFgNode: useCursor(KisCursor::pickerLayerForegroundCursor()); activatePickColor(delayedAction); break; case PickBgNode: useCursor(KisCursor::pickerLayerBackgroundCursor()); activatePickColor(delayedAction); break; case PickFgImage: useCursor(KisCursor::pickerImageForegroundCursor()); activatePickColor(delayedAction); break; case PickBgImage: useCursor(KisCursor::pickerImageBackgroundCursor()); activatePickColor(delayedAction); break; default: break; }; repaintDecorations(); } bool KisToolPaint::isPickingAction(AlternateAction action) { return action == PickFgNode || action == PickBgNode || action == PickFgImage || action == PickBgImage; } void KisToolPaint::deactivateAlternateAction(AlternateAction action) { if (!isPickingAction(action)) { KisTool::deactivateAlternateAction(action); return; } delayedAction = KisTool::NONE; m_colorPickerDelayTimer.stop(); resetCursorStyle(); deactivatePickColor(action); } void KisToolPaint::addPickerJob(const PickingJob &pickingJob) { /** * The actual picking is delayed by a compressor, so we can get this * event when the stroke is already closed */ if (!m_pickerStrokeId) return; KIS_ASSERT_RECOVER_RETURN(isPickingAction(pickingJob.action)); const QPoint imagePoint = image()->documentToImagePixelFloored(pickingJob.documentPixel); const bool fromCurrentNode = pickingJob.action == PickFgNode || pickingJob.action == PickBgNode; m_pickingResource = colorPreviewResourceId(pickingJob.action); KisPaintDeviceSP device = fromCurrentNode ? currentNode()->projection() : image()->projection(); image()->addJob(m_pickerStrokeId, new KisColorPickerStrokeStrategy::Data(device, imagePoint)); } void KisToolPaint::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(!m_pickerStrokeId); setMode(SECONDARY_PAINT_MODE); KisColorPickerStrokeStrategy *strategy = new KisColorPickerStrokeStrategy(); connect(strategy, &KisColorPickerStrokeStrategy::sigColorUpdated, this, &KisToolPaint::slotColorPickingFinished); m_pickerStrokeId = image()->startStroke(strategy); m_colorPickingCompressor->start(PickingJob(event->point, action)); requestUpdateOutline(event->point, event); } else { KisTool::beginAlternateAction(event, action); } } void KisToolPaint::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(m_pickerStrokeId); m_colorPickingCompressor->start(PickingJob(event->point, action)); requestUpdateOutline(event->point, event); } else { KisTool::continueAlternateAction(event, action); } } void KisToolPaint::endAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(m_pickerStrokeId); image()->endStroke(m_pickerStrokeId); m_pickerStrokeId.clear(); requestUpdateOutline(event->point, event); setMode(HOVER_MODE); } else { KisTool::endAlternateAction(event, action); } } int KisToolPaint::colorPreviewResourceId(AlternateAction action) { bool toForegroundColor = action == PickFgNode || action == PickFgImage; int resource = toForegroundColor ? KoCanvasResourceManager::ForegroundColor : KoCanvasResourceManager::BackgroundColor; return resource; } void KisToolPaint::slotColorPickingFinished(const KoColor &color) { canvas()->resourceManager()->setResource(m_pickingResource, color); if (!m_showColorPreview) return; KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); QColor previewColor = kisCanvas->displayColorConverter()->toQColor(color); m_colorPreviewShowComparePlate = true; m_colorPreviewCurrentColor = previewColor; requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } void KisToolPaint::mouseMoveEvent(KoPointerEvent *event) { KisTool::mouseMoveEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } void KisToolPaint::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } QWidget * KisToolPaint::createOptionWidget() { QWidget * optionWidget = new QWidget(); optionWidget->setObjectName(toolId()); QVBoxLayout* verticalLayout = new QVBoxLayout(optionWidget); verticalLayout->setObjectName("KisToolPaint::OptionWidget::VerticalLayout"); verticalLayout->setContentsMargins(0,0,0,0); verticalLayout->setSpacing(5); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(optionWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); verticalLayout->addWidget(specialSpacer); verticalLayout->addWidget(specialSpacer); m_optionsWidgetLayout = new QGridLayout(); m_optionsWidgetLayout->setColumnStretch(1, 1); verticalLayout->addLayout(m_optionsWidgetLayout); m_optionsWidgetLayout->setContentsMargins(0,0,0,0); m_optionsWidgetLayout->setSpacing(5); if (!quickHelp().isEmpty()) { QPushButton* push = new QPushButton(KisIconUtils::loadIcon("help-contents"), QString(), optionWidget); connect(push, SIGNAL(clicked()), this, SLOT(slotPopupQuickHelp())); QHBoxLayout* hLayout = new QHBoxLayout(optionWidget); hLayout->addWidget(push); hLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed)); verticalLayout->addLayout(hLayout); } return optionWidget; } QWidget* findLabelWidget(QGridLayout *layout, QWidget *control) { QWidget *result = 0; int index = layout->indexOf(control); int row, col, rowSpan, colSpan; layout->getItemPosition(index, &row, &col, &rowSpan, &colSpan); if (col > 0) { QLayoutItem *item = layout->itemAtPosition(row, col - 1); if (item) { result = item->widget(); } } else { QLayoutItem *item = layout->itemAtPosition(row, col + 1); if (item) { result = item->widget(); } } return result; } void KisToolPaint::showControl(QWidget *control, bool value) { control->setVisible(value); QWidget *label = findLabelWidget(m_optionsWidgetLayout, control); if (label) { label->setVisible(value); } } void KisToolPaint::enableControl(QWidget *control, bool value) { control->setEnabled(value); QWidget *label = findLabelWidget(m_optionsWidgetLayout, control); if (label) { label->setEnabled(value); } } void KisToolPaint::addOptionWidgetLayout(QLayout *layout) { Q_ASSERT(m_optionsWidgetLayout != 0); int rowCount = m_optionsWidgetLayout->rowCount(); m_optionsWidgetLayout->addLayout(layout, rowCount, 0, 1, 2); } void KisToolPaint::addOptionWidgetOption(QWidget *control, QWidget *label) { Q_ASSERT(m_optionsWidgetLayout != 0); if (label) { m_optionsWidgetLayout->addWidget(label, m_optionsWidgetLayout->rowCount(), 0); m_optionsWidgetLayout->addWidget(control, m_optionsWidgetLayout->rowCount() - 1, 1); } else { m_optionsWidgetLayout->addWidget(control, m_optionsWidgetLayout->rowCount(), 0, 1, 2); } } void KisToolPaint::setOpacity(qreal opacity) { m_opacity = quint8(opacity * OPACITY_OPAQUE_U8); } const KoCompositeOp* KisToolPaint::compositeOp() { if (currentNode()) { KisPaintDeviceSP device = currentNode()->paintDevice(); if (device) { QString op = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentCompositeOp).toString(); return device->colorSpace()->compositeOp(op); } } return 0; } void KisToolPaint::slotPopupQuickHelp() { QWhatsThis::showText(QCursor::pos(), quickHelp()); } void KisToolPaint::updateTabletPressureSamples() { KisConfig cfg; KisCubicCurve curve; curve.fromString(cfg.pressureTabletCurve()); m_pressureSamples = curve.floatTransfer(LEVEL_OF_PRESSURE_RESOLUTION + 1); } void KisToolPaint::setupPaintAction(KisRecordedPaintAction* action) { KisTool::setupPaintAction(action); action->setOpacity(m_opacity / qreal(255.0)); const KoCompositeOp* op = compositeOp(); if (op) { action->setCompositeOp(op->id()); } } KisToolPaint::NodePaintAbility KisToolPaint::nodePaintAbility() { KisNodeSP node = currentNode(); if (!node) { return NONE; } if (node->inherits("KisShapeLayer")) { return VECTOR; } if (node->paintDevice()) { return PAINT; } return NONE; } void KisToolPaint::activatePrimaryAction() { pickColorWasOverridden(); setOutlineEnabled(true); KisTool::activatePrimaryAction(); } void KisToolPaint::deactivatePrimaryAction() { setOutlineEnabled(false); KisTool::deactivatePrimaryAction(); } bool KisToolPaint::isOutlineEnabled() const { return m_isOutlineEnabled; } void KisToolPaint::setOutlineEnabled(bool value) { m_isOutlineEnabled = value; requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::increaseBrushSize() { qreal paintopSize = currentPaintOpPreset()->settings()->paintOpSize(); std::vector::iterator result = std::upper_bound(m_standardBrushSizes.begin(), m_standardBrushSizes.end(), qRound(paintopSize)); int newValue = result != m_standardBrushSizes.end() ? *result : m_standardBrushSizes.back(); currentPaintOpPreset()->settings()->setPaintOpSize(newValue); requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::decreaseBrushSize() { qreal paintopSize = currentPaintOpPreset()->settings()->paintOpSize(); std::vector::reverse_iterator result = std::upper_bound(m_standardBrushSizes.rbegin(), m_standardBrushSizes.rend(), (int)paintopSize, std::greater()); int newValue = result != m_standardBrushSizes.rend() ? *result : m_standardBrushSizes.front(); currentPaintOpPreset()->settings()->setPaintOpSize(newValue); requestUpdateOutline(m_outlineDocPoint, 0); } QRectF KisToolPaint::colorPreviewDocRect(const QPointF &outlineDocPoint) { if (!m_showColorPreview) return QRect(); KisConfig cfg; const QRectF colorPreviewViewRect = cfg.colorPreviewRect(); const QRectF colorPreviewDocumentRect = canvas()->viewConverter()->viewToDocument(colorPreviewViewRect); return colorPreviewDocumentRect.translated(outlineDocPoint); } void KisToolPaint::requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event) { if (!m_supportOutline) return; KisConfig cfg; KisPaintOpSettings::OutlineMode outlineMode; outlineMode = KisPaintOpSettings::CursorNoOutline; if (isOutlineEnabled() && (mode() == KisTool::GESTURE_MODE || ((cfg.newOutlineStyle() == OUTLINE_FULL || cfg.newOutlineStyle() == OUTLINE_CIRCLE || cfg.newOutlineStyle() == OUTLINE_TILT || cfg.newOutlineStyle() == OUTLINE_COLOR ) && ((mode() == HOVER_MODE) || (mode() == PAINT_MODE && cfg.showOutlineWhilePainting()))))) { // lisp forever! if(cfg.newOutlineStyle() == OUTLINE_CIRCLE) { outlineMode = KisPaintOpSettings::CursorIsCircleOutline; } else if(cfg.newOutlineStyle() == OUTLINE_TILT) { outlineMode = KisPaintOpSettings::CursorTiltOutline; } else if(cfg.newOutlineStyle() == OUTLINE_COLOR) { outlineMode = KisPaintOpSettings::CursorColorOutline; } else { outlineMode = KisPaintOpSettings::CursorIsOutline; } } m_outlineDocPoint = outlineDocPoint; m_currentOutline = getOutlinePath(m_outlineDocPoint, event, outlineMode); QRectF outlinePixelRect = m_currentOutline.boundingRect(); QRectF outlineDocRect = currentImage()->pixelToDocument(outlinePixelRect); // This adjusted call is needed as we paint with a 3 pixel wide brush and the pen is outside the bounds of the path // Pen uses view coordinates so we have to zoom the document value to match 2 pixel in view coordiates // See BUG 275829 qreal zoomX; qreal zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); qreal xoffset = 2.0/zoomX; qreal yoffset = 2.0/zoomY; if (!outlineDocRect.isEmpty()) { outlineDocRect.adjust(-xoffset,-yoffset,xoffset,yoffset); } QRectF colorPreviewDocRect = this->colorPreviewDocRect(m_outlineDocPoint); QRectF colorPreviewDocUpdateRect; if (!colorPreviewDocRect.isEmpty()) { colorPreviewDocUpdateRect.adjust(-xoffset,-yoffset,xoffset,yoffset); } // DIRTY HACK ALERT: we should fetch the assistant's dirty rect when requesting // the update, instead of just dumbly update the entire canvas! KisCanvas2 * kiscanvas = dynamic_cast(canvas()); KisPaintingAssistantsDecorationSP decoration = kiscanvas->paintingAssistantsDecoration(); if (decoration && decoration->visible()) { kiscanvas->updateCanvas(); } else { // TODO: only this branch should be present! if (!m_oldColorPreviewUpdateRect.isEmpty()) { canvas()->updateCanvas(m_oldColorPreviewUpdateRect); } if (!m_oldOutlineRect.isEmpty()) { canvas()->updateCanvas(m_oldOutlineRect); } if (!outlineDocRect.isEmpty()) { canvas()->updateCanvas(outlineDocRect); } if (!colorPreviewDocUpdateRect.isEmpty()) { canvas()->updateCanvas(colorPreviewDocUpdateRect); } } m_oldOutlineRect = outlineDocRect; m_oldColorPreviewRect = colorPreviewDocRect; m_oldColorPreviewUpdateRect = colorPreviewDocUpdateRect; } QPainterPath KisToolPaint::getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) { Q_UNUSED(event); QPointF imagePos = currentImage()->documentToPixel(documentPos); QPainterPath path = currentPaintOpPreset()->settings()-> brushOutline(KisPaintInformation(imagePos), outlineMode); return path; } diff --git a/libs/ui/widgets/kis_gradient_slider.cpp b/libs/ui/widgets/kis_gradient_slider.cpp index b2580eeeb5..16c0619c38 100644 --- a/libs/ui/widgets/kis_gradient_slider.cpp +++ b/libs/ui/widgets/kis_gradient_slider.cpp @@ -1,371 +1,401 @@ /* * This file is part of Krita * * Copyright (c) 2006 Frederic Coiffier * * 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. */ // Local includes. #include "kis_gradient_slider.h" // C++ includes. #include #include // Qt includes. #include #include #include #include #include #include #include #include #define MARGIN 5 #define HANDLE_SIZE 10 KisGradientSlider::KisGradientSlider(QWidget *parent) : QWidget(parent) , m_leftmost(0) , m_rightmost(0) , m_scalingFactor(0) , m_blackCursor(0) , m_whiteCursor(0) , m_gammaCursor(0) , m_black(0) , m_white(255) , m_gamma(1.0) , m_gammaEnabled(false) , m_whiteEnabled(true) , m_feedback(false) + , m_inverted(false) { m_grabCursor = None; setMouseTracking(true); setFocusPolicy(Qt::StrongFocus); } KisGradientSlider::~KisGradientSlider() { } int KisGradientSlider::black() const { return m_black; } int KisGradientSlider::white() const { return m_white; } void KisGradientSlider::paintEvent(QPaintEvent *e) { QWidget::paintEvent(e); int x, y; int wWidth = width() - (2 * MARGIN); int wHeight = height(); const int gradientHeight = qRound((double)wHeight / 7.0 * 2); QPainter p1(this); p1.fillRect(rect(), palette().background()); p1.setPen(Qt::black); p1.drawRect(MARGIN, MARGIN, wWidth, height() - 2 * MARGIN - HANDLE_SIZE); // Draw first gradient - QLinearGradient grayGradient(MARGIN, 0, wWidth, gradientHeight); - grayGradient.setColorAt(0, Qt::black); - grayGradient.setColorAt(1, Qt::white); + QLinearGradient grayGradient(MARGIN, y, wWidth, gradientHeight); + grayGradient.setColorAt(0, m_inverted ? Qt::white : Qt::black); + grayGradient.setColorAt(1, m_inverted ? Qt::black : Qt::white); p1.fillRect(MARGIN, 0, wWidth, gradientHeight, QBrush(grayGradient)); // Draw second gradient y = gradientHeight; p1.fillRect(MARGIN, y, wWidth, gradientHeight, Qt::white); - if (m_blackCursor > 0) { + if (m_blackCursor > 0 && !m_inverted) { p1.fillRect(MARGIN, y, m_blackCursor, gradientHeight, Qt::black); + } else if (m_blackCursor < wWidth && m_inverted) { + p1.fillRect(MARGIN + m_blackCursor, y, wWidth - m_blackCursor, gradientHeight, Qt::black); } - for (x = (int)m_blackCursor + MARGIN; x < (int)m_whiteCursor - MARGIN; ++x) { - double inten = (double)(x - (m_blackCursor + MARGIN)) / (double)((m_whiteCursor - MARGIN) - (m_blackCursor + MARGIN)); + + int left = qMin(m_blackCursor, m_whiteCursor); + int right = qMax(m_blackCursor, m_whiteCursor); + for (x = left; x <= right; ++x) { + double inten = (double)(x - m_blackCursor) / + (double)(m_whiteCursor - m_blackCursor); inten = pow(inten, (1.0 / m_gamma)); int gray = (int)(255 * inten); p1.setPen(QColor(gray, gray, gray)); - p1.drawLine(x, y, x, y + gradientHeight - 1); + p1.drawLine(x + MARGIN, y, x + MARGIN, y + gradientHeight - 1); } // Draw cursors y += gradientHeight; QPoint a[3]; p1.setPen(Qt::darkGray); p1.setRenderHint(QPainter::Antialiasing, true); const int cursorHalfBase = (int)(gradientHeight / 1.5); a[0] = QPoint(m_blackCursor + MARGIN, y); a[1] = QPoint(m_blackCursor + MARGIN + cursorHalfBase, wHeight - 1); a[2] = QPoint(m_blackCursor + MARGIN - cursorHalfBase, wHeight - 1); p1.setBrush(Qt::black); p1.drawPolygon(a, 3); p1.setPen(Qt::black); if (m_gammaEnabled) { - a[0] = QPoint(m_gammaCursor, y); - a[1] = QPoint(m_gammaCursor + cursorHalfBase, wHeight - 1); - a[2] = QPoint(m_gammaCursor - cursorHalfBase, wHeight - 1); + a[0] = QPoint(m_gammaCursor + MARGIN, y); + a[1] = QPoint(m_gammaCursor + MARGIN + cursorHalfBase, wHeight - 1); + a[2] = QPoint(m_gammaCursor + MARGIN - cursorHalfBase, wHeight - 1); p1.setBrush(Qt::gray); p1.drawPolygon(a, 3); } if (m_whiteEnabled) { - a[0] = QPoint(m_whiteCursor - MARGIN, y); - a[1] = QPoint(m_whiteCursor - MARGIN + cursorHalfBase, wHeight - 1); - a[2] = QPoint(m_whiteCursor - MARGIN - cursorHalfBase, wHeight - 1); + a[0] = QPoint(m_whiteCursor + MARGIN, y); + a[1] = QPoint(m_whiteCursor + MARGIN + cursorHalfBase, wHeight - 1); + a[2] = QPoint(m_whiteCursor + MARGIN - cursorHalfBase, wHeight - 1); p1.setBrush(Qt::white); p1.drawPolygon(a, 3); } } void KisGradientSlider::resizeEvent(QResizeEvent *) { - m_scalingFactor = (double)(width() - MARGIN) / 255; + m_scalingFactor = (double)(width() - 2 * MARGIN) / 255; calculateCursorPositions(); update(); } -void KisGradientSlider::mousePressEvent(QMouseEvent * e) +void KisGradientSlider::mousePressEvent(QMouseEvent *e) { eCursor closest_cursor; int distance; if (e->button() != Qt::LeftButton) return; unsigned int x = e->pos().x(); - int xPlusMargin = x + MARGIN; + int xMinusMargin = x - MARGIN; distance = width() + 1; // just a big number - if (abs((int)(xPlusMargin - m_blackCursor)) < distance) { - distance = abs((int)(xPlusMargin - m_blackCursor)); + if (abs((int)(xMinusMargin - m_blackCursor)) < distance) { + distance = abs((int)(xMinusMargin - m_blackCursor)); closest_cursor = BlackCursor; } - if (abs((int)(xPlusMargin - m_whiteCursor)) < distance) { - distance = abs((int)(xPlusMargin - m_whiteCursor)); + if (abs((int)(xMinusMargin - m_whiteCursor)) < distance) { + distance = abs((int)(xMinusMargin - m_whiteCursor)); closest_cursor = WhiteCursor; } if (m_gammaEnabled) { - int gammaDistance = (int)xPlusMargin - m_gammaCursor; + int gammaDistance = (int)xMinusMargin - m_gammaCursor; if (abs(gammaDistance) < distance) { - distance = abs((int)xPlusMargin - m_gammaCursor); + distance = abs((int)xMinusMargin - m_gammaCursor); closest_cursor = GammaCursor; } else if (abs(gammaDistance) == distance) { if ((closest_cursor == BlackCursor) && (gammaDistance > 0)) { distance = abs(gammaDistance); closest_cursor = GammaCursor; } else if ((closest_cursor == WhiteCursor) && (gammaDistance < 0)) { distance = abs(gammaDistance); closest_cursor = GammaCursor; } } } if (distance > 20) { m_grabCursor = None; return; } // Determine cursor values and the leftmost and rightmost points. switch (closest_cursor) { case BlackCursor: - m_blackCursor = x - MARGIN; + m_blackCursor = xMinusMargin; m_grabCursor = closest_cursor; - m_leftmost = 0; - m_rightmost = m_whiteCursor - ((MARGIN + 1) * m_scalingFactor); + if (m_inverted) { + m_leftmost = m_whiteCursor + 1; + m_rightmost = width() - 2 * MARGIN - 1; + } else { + m_leftmost = 0; + m_rightmost = m_whiteCursor - 1; + } if (m_gammaEnabled) m_gammaCursor = calculateGammaCursor(); break; case WhiteCursor: - m_whiteCursor = x + MARGIN; + m_whiteCursor = xMinusMargin; m_grabCursor = closest_cursor; - m_leftmost = m_blackCursor + (MARGIN * m_scalingFactor); - m_rightmost = width() - MARGIN ; + if (m_inverted) { + m_leftmost = 0; + m_rightmost = m_blackCursor - 1; + } else { + m_leftmost = m_blackCursor + 1; + m_rightmost = width() - 2 * MARGIN - 1; + } if (m_gammaEnabled) m_gammaCursor = calculateGammaCursor(); break; case GammaCursor: m_gammaCursor = x; m_grabCursor = closest_cursor; - m_leftmost = m_blackCursor + (MARGIN * m_scalingFactor); - m_rightmost = m_whiteCursor - (MARGIN * m_scalingFactor); + m_leftmost = qMin(m_blackCursor, m_whiteCursor); + m_rightmost = qMax(m_blackCursor, m_whiteCursor); { double delta = (double)(m_whiteCursor - m_blackCursor) / 2.0; double mid = (double)m_blackCursor + delta + MARGIN; - double tmp = (x - mid) / delta; + double tmp = (xMinusMargin - mid) / delta; m_gamma = 1.0 / pow(10, tmp); } break; default: break; } update(); } void KisGradientSlider::mouseReleaseEvent(QMouseEvent * e) { if (e->button() != Qt::LeftButton) return; update(); switch (m_grabCursor) { case BlackCursor: - m_black = qRound( m_blackCursor / m_scalingFactor); + m_black = qRound(m_blackCursor / m_scalingFactor); m_feedback = true; emit sigModifiedBlack(m_black); break; case WhiteCursor: - m_white = qRound( (m_whiteCursor - MARGIN) / m_scalingFactor); + m_white = qRound(m_whiteCursor / m_scalingFactor); m_feedback = true; emit sigModifiedWhite(m_white); break; case GammaCursor: emit sigModifiedGamma(m_gamma); break; default: break; } m_grabCursor = None; m_feedback = false; } void KisGradientSlider::mouseMoveEvent(QMouseEvent * e) { int x = e->pos().x(); - + int xMinusMargin = x - MARGIN; if (m_grabCursor != None) { // Else, drag the selected point - if (x + MARGIN <= m_leftmost) - x = m_leftmost; + if (xMinusMargin <= m_leftmost) + xMinusMargin = m_leftmost; - if (x >= m_rightmost) - x = m_rightmost; + if (xMinusMargin >= m_rightmost) + xMinusMargin = m_rightmost; switch (m_grabCursor) { case BlackCursor: - if (m_blackCursor != x) { - m_blackCursor = x; + if (m_blackCursor != xMinusMargin) { + m_blackCursor = xMinusMargin; if (m_gammaEnabled) { m_gammaCursor = calculateGammaCursor(); } } break; case WhiteCursor: - if (m_whiteCursor != x) { - m_whiteCursor = x + MARGIN; + if (m_whiteCursor != xMinusMargin) { + m_whiteCursor = xMinusMargin; if (m_gammaEnabled) { m_gammaCursor = calculateGammaCursor(); } } break; case GammaCursor: - if (m_gammaCursor != x) { - m_gammaCursor = x; + if (m_gammaCursor != xMinusMargin) { + m_gammaCursor = xMinusMargin; double delta = (double)(m_whiteCursor - m_blackCursor) / 2.0; double mid = (double)m_blackCursor + delta; - double tmp = (x - mid) / delta; + double tmp = (xMinusMargin - mid) / delta; m_gamma = 1.0 / pow(10, tmp); } break; default: break; } } update(); } void KisGradientSlider::calculateCursorPositions() { m_blackCursor = qRound(m_black * m_scalingFactor); - m_whiteCursor = qRound(m_white * m_scalingFactor + MARGIN); + m_whiteCursor = qRound(m_white * m_scalingFactor); m_gammaCursor = calculateGammaCursor(); } unsigned int KisGradientSlider::calculateGammaCursor() { double delta = (double)(m_whiteCursor - m_blackCursor) / 2.0; - double mid = (double)m_blackCursor + delta; - double tmp = log10(1.0 / m_gamma); + double mid = (double)m_blackCursor + delta; + double tmp = log10(1.0 / m_gamma); return (unsigned int)qRound(mid + delta * tmp); } void KisGradientSlider::enableGamma(bool b) { m_gammaEnabled = b; update(); } double KisGradientSlider::getGamma(void) { return m_gamma; } void KisGradientSlider::enableWhite(bool b) { m_whiteEnabled = b; update(); } +void KisGradientSlider::setInverted(bool b) +{ + m_inverted = b; + update(); +} + void KisGradientSlider::slotModifyBlack(int v) { - if (v >= 0 && v <= (int)m_white && !m_feedback) { - m_black = v; - m_blackCursor = qRound(m_black * m_scalingFactor); - m_gammaCursor = calculateGammaCursor(); - update(); - } + if ((m_inverted && (v < m_white || v > width())) || + (!m_inverted && (v < 0 || v > m_white)) || + m_feedback) + return; + + m_black = v; + m_blackCursor = qRound(m_black * m_scalingFactor); + m_gammaCursor = calculateGammaCursor(); + update(); } + void KisGradientSlider::slotModifyWhite(int v) { - if (v >= (int)m_black && v <= width() && !m_feedback) { - m_white = v; - m_whiteCursor = qRound(m_white * m_scalingFactor + MARGIN); - m_gammaCursor = calculateGammaCursor(); - update(); - } + if ((m_inverted && (v < 0 || v > m_white)) || + (!m_inverted && (v < m_black && v > width())) || + m_feedback) + return; + m_white = v; + m_whiteCursor = qRound(m_white * m_scalingFactor); + m_gammaCursor = calculateGammaCursor(); + update(); } + void KisGradientSlider::slotModifyGamma(double v) { if (m_gamma != v) { emit sigModifiedGamma(v); } m_gamma = v; m_gammaCursor = calculateGammaCursor(); update(); } diff --git a/libs/ui/widgets/kis_gradient_slider.h b/libs/ui/widgets/kis_gradient_slider.h index 97a09e828c..2a8450aabf 100644 --- a/libs/ui/widgets/kis_gradient_slider.h +++ b/libs/ui/widgets/kis_gradient_slider.h @@ -1,98 +1,101 @@ /* * This file is part of Krita * * Copyright (c) 2006 Frederic Coiffier * * 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_GRADIENT_SLIDER_H #define KIS_GRADIENT_SLIDER_H // Qt includes. #include #include #include #include #include class KRITAUI_EXPORT KisGradientSlider : public QWidget { Q_OBJECT typedef enum { BlackCursor, GammaCursor, WhiteCursor, None } eCursor; public: KisGradientSlider(QWidget *parent = 0); ~KisGradientSlider() override; int black() const; int white() const; public Q_SLOTS: void slotModifyBlack(int); void slotModifyWhite(int); void slotModifyGamma(double); Q_SIGNALS: void sigModifiedBlack(int); void sigModifiedWhite(int); void sigModifiedGamma(double); protected: void paintEvent(QPaintEvent *) override; void resizeEvent(QResizeEvent *) override; void mousePressEvent(QMouseEvent * e) override; void mouseReleaseEvent(QMouseEvent * e) override; void mouseMoveEvent(QMouseEvent * e) override; private: void calculateCursorPositions(); unsigned int calculateGammaCursor(); public: void enableGamma(bool b); double getGamma(void); void enableWhite(bool b); + void setInverted(bool b); + private: int m_leftmost; int m_rightmost; eCursor m_grabCursor; double m_scalingFactor; int m_blackCursor; int m_whiteCursor; int m_gammaCursor; int m_black; int m_white; double m_gamma; bool m_gammaEnabled; bool m_whiteEnabled; bool m_feedback; + bool m_inverted; }; #endif /* KIS_GRADIENT_SLIDER_H */ diff --git a/libs/ui/widgets/kis_paintop_presets_popup.cpp b/libs/ui/widgets/kis_paintop_presets_popup.cpp index 535bf4b751..a2f0d2e66a 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.cpp +++ b/libs/ui/widgets/kis_paintop_presets_popup.cpp @@ -1,772 +1,774 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * 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 "widgets/kis_paintop_presets_popup.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_resource_server_provider.h" #include "kis_lod_availability_widget.h" #include "kis_signal_auto_connection.h" #include // ones from brush engine selector #include #include struct KisPaintOpPresetsPopup::Private { public: Ui_WdgPaintOpSettings uiWdgPaintOpPresetSettings; QGridLayout *layout; KisPaintOpConfigWidget *settingsWidget; QFont smallFont; KisCanvasResourceProvider *resourceProvider; KisFavoriteResourceManager *favoriteResManager; bool detached; bool ignoreHideEvents; QSize minimumSettingsWidgetSize; QRect detachedGeometry; KisSignalAutoConnectionsStore widgetConnections; }; KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, KisFavoriteResourceManager* favoriteResourceManager, KisPresetSaveWidget* savePresetWidget, QWidget * parent) : QWidget(parent) , m_d(new Private()) { setObjectName("KisPaintOpPresetsPopup"); setFont(KoDockRegistry::dockFont()); current_paintOpId = ""; m_d->resourceProvider = resourceProvider; m_d->favoriteResManager = favoriteResourceManager; m_d->uiWdgPaintOpPresetSettings.setupUi(this); m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer); m_d->layout->setSizeConstraint(QLayout::SetFixedSize); m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white); m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200)); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setIcon(KisIconUtils::loadIcon("updateColorize")); // refresh icon m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setIcon(KisIconUtils::loadIcon("dirty-preset")); // edit icon m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setIcon(KisIconUtils::loadIcon("warning")); m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setToolTip(i18n("The settings for this preset have changed from their default.")); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setToolTip(i18n("Toggle showing presets")); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setToolTip(i18n("Toggle showing scratchpad")); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setToolTip(i18n("Reload the brush preset")); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setToolTip(i18n("Rename the brush preset")); // overwrite existing preset and saving a new preset use the same dialog saveDialog = savePresetWidget; saveDialog->scratchPadSetup(resourceProvider); saveDialog->setFavoriteResourceManager(m_d->favoriteResManager); // this is needed when saving the preset saveDialog->hide(); // the area on the brush editor for renaming the brush. make sure edit fields are hidden by default toggleBrushRenameUIActive(false); // DETAIL and THUMBNAIL view changer QMenu* menu = new QMenu(this); menu->setStyleSheet("margin: 6px"); menu->addSection(i18n("Display")); QActionGroup *actionGroup = new QActionGroup(this); KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig().presetChooserViewMode(); QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotThumbnailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::THUMBNAIL); action->setActionGroup(actionGroup); action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotDetailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::DETAIL); action->setActionGroup(actionGroup); // add horizontal slider for the icon size QSlider* iconSizeSlider = new QSlider(this); iconSizeSlider->setOrientation(Qt::Horizontal); iconSizeSlider->setRange(30, 80); iconSizeSlider->setValue(m_d->uiWdgPaintOpPresetSettings.presetWidget->iconSize()); iconSizeSlider->setMinimumHeight(20); iconSizeSlider->setMinimumWidth(40); iconSizeSlider->setTickInterval(10); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(iconSizeSlider); menu->addSection(i18n("Icon Size")); menu->addAction(sliderAction); // configure the button and assign menu m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setMenu(menu); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setPopupMode(QToolButton::InstantPopup); // show/hide buttons KisConfig cfg; m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible()); if (cfg.scratchpadVisible()) { slotSwitchScratchpad(true); // show scratchpad } else { slotSwitchScratchpad(false); } m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false); slotSwitchShowPresets(false); // hide presets by default // Connections connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage())); connect(saveDialog, SIGNAL(resourceSelected(KoResource*)), this, SLOT(resourceSelected(KoResource*))); connect (m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotRenameBrushActivated())); connect (m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton, SIGNAL(clicked(bool)), this, SLOT(slotRenameBrushDeactivated())); connect(m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton, SIGNAL(clicked(bool)), this, SLOT(slotSaveRenameCurrentBrush())); connect(m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField, SIGNAL(returnPressed()), SLOT(slotSaveRenameCurrentBrush())); connect(iconSizeSlider, SIGNAL(sliderMoved(int)), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSetIconSize(int))); connect(iconSizeSlider, SIGNAL(sliderReleased()), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSaveIconSize())); connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchScratchpad(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault())); connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillLayer())); connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient())); connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground())); m_d->settingsWidget = 0; setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); connect(m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton, SIGNAL(clicked()), this, SLOT(slotSaveBrushPreset())); connect(m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton, SIGNAL(clicked()), this, SLOT(slotSaveNewBrushPreset())); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), this, SIGNAL(reloadPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), this, SIGNAL(defaultPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(dirtyPresetToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushSizeToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushOpacityToggled(bool))); // preset widget connections connect(m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(signalResourceSelected(KoResource*))); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), SLOT(slotUpdatePresetSettings())); m_d->detached = false; m_d->ignoreHideEvents = false; m_d->minimumSettingsWidgetSize = QSize(0, 0); m_d->detachedGeometry = QRect(100, 100, 0, 0); m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets()); m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize()); m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity()); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setCanvasResourceManager(resourceProvider->resourceManager()); connect(resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(slotResourceChanged(int, QVariant))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodAvailability(bool)), SLOT(slotLodAvailabilityChanged(bool))); slotResourceChanged(KisCanvasResourceProvider::LodAvailability, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodAvailability)); connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePaintOpFilter())); // setup things like the scene construct images, layers, etc that is a one-time thing m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setup(); } void KisPaintOpPresetsPopup::slotRenameBrushActivated() { toggleBrushRenameUIActive(true); } void KisPaintOpPresetsPopup::slotRenameBrushDeactivated() { toggleBrushRenameUIActive(false); } void KisPaintOpPresetsPopup::toggleBrushRenameUIActive(bool isRenaming) { // This function doesn't really do anything except get the UI in a state to rename a brush preset m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setVisible(isRenaming); m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton->setVisible(isRenaming); m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton->setVisible(isRenaming); // hide these below areas while renaming m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setEnabled(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setVisible(!isRenaming); // if the presets area is shown, only then can you show/hide the load default brush // need to think about weird state when you are in the middle of renaming a brush // what happens if you try to change presets. maybe we should auto-hide (or disable) // the presets area in this case if (m_d->uiWdgPaintOpPresetSettings.presetWidget->isVisible()) { m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset->setVisible(!isRenaming); } } void KisPaintOpPresetsPopup::slotSaveRenameCurrentBrush() { // if you are renaming a brush, that is different than updating the settings // make sure we are in a clean state before renaming. This logic might change, // but that is what we are going with for now emit reloadPresetClicked(); m_d->favoriteResManager->setBlockUpdates(true); // get a reference to the existing (and new) file name and path that we are working with KisPaintOpPresetSP curPreset = m_d->resourceProvider->currentPreset(); if (!curPreset) return; KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QString saveLocation = rServer->saveLocation(); QString originalPresetName = curPreset->name(); QString renamedPresetName = m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->text(); QString originalPresetPathAndFile = saveLocation + originalPresetName + curPreset->defaultFileExtension(); QString renamedPresetPathAndFile = saveLocation + renamedPresetName + curPreset->defaultFileExtension(); // create a new brush preset with the name specified and add to resource provider KisPaintOpPresetSP newPreset = curPreset->clone(); newPreset->setFilename(renamedPresetPathAndFile); // this also contains the path newPreset->setName(renamedPresetName); newPreset->setImage(curPreset->image()); // use existing thumbnail (might not need to do this) newPreset->setPresetDirty(false); newPreset->setValid(true); rServer->addResource(newPreset); resourceSelected(newPreset.data()); // refresh and select our freshly renamed resource // Now blacklist the original file if (rServer->resourceByName(originalPresetName)) { rServer->removeResourceAndBlacklist(curPreset); } m_d->favoriteResManager->setBlockUpdates(false); toggleBrushRenameUIActive(false); // this returns the UI to its original state after saving slotUpdatePresetSettings(); // update visibility of dirty preset and icon } void KisPaintOpPresetsPopup::slotResourceChanged(int key, const QVariant &value) { if (key == KisCanvasResourceProvider::LodAvailability) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodAvailability(value.toBool()); } } void KisPaintOpPresetsPopup::slotLodAvailabilityChanged(bool value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodAvailability, QVariant(value)); } KisPaintOpPresetsPopup::~KisPaintOpPresetsPopup() { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->settingsWidget->hide(); m_d->settingsWidget->setParent(0); m_d->settingsWidget = 0; } delete m_d; } void KisPaintOpPresetsPopup::setPaintOpSettingsWidget(QWidget * widget) { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry(); } m_d->layout->update(); updateGeometry(); m_d->widgetConnections.clear(); m_d->settingsWidget = 0; if (widget) { m_d->settingsWidget = dynamic_cast(widget); KIS_ASSERT_RECOVER_RETURN(m_d->settingsWidget); KisConfig cfg; if (m_d->settingsWidget->supportScratchBox() && cfg.scratchpadVisible()) { slotSwitchScratchpad(true); } else { slotSwitchScratchpad(false); } m_d->widgetConnections.addConnection(m_d->settingsWidget, SIGNAL(sigConfigurationItemChanged()), this, SLOT(slotUpdateLodAvailability())); widget->setFont(m_d->smallFont); QSize hint = widget->sizeHint(); m_d->minimumSettingsWidgetSize = QSize(qMax(hint.width(), m_d->minimumSettingsWidgetSize.width()), qMax(hint.height(), m_d->minimumSettingsWidgetSize.height())); widget->setMinimumSize(m_d->minimumSettingsWidgetSize); m_d->layout->addWidget(widget); // hook up connections that will monitor if our preset is dirty or not. Show a notification if it is if (m_d->resourceProvider && m_d->resourceProvider->currentPreset() ) { KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset(); m_d->widgetConnections.addConnection(preset->updateProxy(), SIGNAL(sigSettingsChanged()), this, SLOT(slotUpdatePresetSettings())); } m_d->layout->update(); widget->show(); } slotUpdateLodAvailability(); } void KisPaintOpPresetsPopup::slotUpdateLodAvailability() { if (!m_d->settingsWidget) return; KisPaintopLodLimitations l = m_d->settingsWidget->lodLimitations(); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLimitations(l); } QImage KisPaintOpPresetsPopup::cutOutOverlay() { return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay(); } void KisPaintOpPresetsPopup::contextMenuEvent(QContextMenuEvent *e) { Q_UNUSED(e); } void KisPaintOpPresetsPopup::switchDetached(bool show) { if (parentWidget()) { m_d->detached = !m_d->detached; if (m_d->detached) { m_d->ignoreHideEvents = true; if (show) { parentWidget()->show(); } m_d->ignoreHideEvents = false; } else { KisConfig cfg; parentWidget()->hide(); } KisConfig cfg; cfg.setPaintopPopupDetached(m_d->detached); } } void KisPaintOpPresetsPopup::resourceSelected(KoResource* resource) { // this gets called every time the brush editor window is opened // TODO: this gets called multiple times whenever the preset is changed in the presets area // the connections probably need to be thought about with this a bit more to keep things in sync m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(resource); // find the display name of the brush engine and append it to the selected preset display QString currentBrushEngineName; for(int i=0; i < sortedBrushEnginesList.length(); i++) { if (sortedBrushEnginesList.at(i).id == currentPaintOpId() ) { currentBrushEngineName = sortedBrushEnginesList.at(i).name; } } - QString selectedBrush = resource->name(); + // brush names have underscores as part of the file name (to help with building). We don't really need underscores + // when viewing the names, so replace them with spaces + QString formattedBrushName = resource->name().replace("_", " "); - m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(selectedBrush); + m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(formattedBrushName); m_d->uiWdgPaintOpPresetSettings.currentBrushEngineLabel->setText(currentBrushEngineName.append(" ").append("Engine")); - m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setText(resource->name()); + m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setText(resource->name()); // use file name // get the preset image and pop it into the thumbnail area on the top of the brush editor QGraphicsScene * thumbScene = new QGraphicsScene(this); thumbScene->addPixmap(QPixmap::fromImage(resource->image().scaled(55, 55))); thumbScene->setSceneRect(0, 0, 55, 55); // 55 x 55 image for thumb. this is also set in the UI m_d->uiWdgPaintOpPresetSettings.presetThumbnailicon->setScene(thumbScene); toggleBrushRenameUIActive(false); // reset the UI state of renaming a brush if we are changing brush presets slotUpdatePresetSettings(); // check to see if the dirty preset icon needs to be shown } bool variantLessThan(const KisPaintOpInfo v1, const KisPaintOpInfo v2) { return v1.priority < v2.priority; } void KisPaintOpPresetsPopup::setPaintOpList(const QList< KisPaintOpFactory* >& list) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->clear(); // reset combobox list just in case // create a new list so we can sort it and populate the brush engine combo box sortedBrushEnginesList.clear(); // just in case this function is called again, don't keep adding to the list for(int i=0; i < list.length(); i++) { QString fileName = KoResourcePaths::findResource("kis_images", list.at(i)->pixmap()); QPixmap pixmap(fileName); if(pixmap.isNull()){ pixmap = QPixmap(22,22); pixmap.fill(); } KisPaintOpInfo paintOpInfo; paintOpInfo.id = list.at(i)->id(); paintOpInfo.name = list.at(i)->name(); paintOpInfo.icon = pixmap; paintOpInfo.priority = list.at(i)->priority(); sortedBrushEnginesList.append(paintOpInfo); } std::stable_sort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan ); // add an "All" option at the front to show all presets QPixmap emptyPixmap = QPixmap(22,22); emptyPixmap.fill(palette().color(QPalette::Background)); sortedBrushEnginesList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), emptyPixmap, 0 )); // fill the list into the brush combo box for (int m = 0; m < sortedBrushEnginesList.length(); m++) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->addItem(sortedBrushEnginesList[m].icon, sortedBrushEnginesList[m].name, QVariant(sortedBrushEnginesList[m].id)); } } void KisPaintOpPresetsPopup::setCurrentPaintOpId(const QString& paintOpId) { current_paintOpId = paintOpId; } QString KisPaintOpPresetsPopup::currentPaintOpId() { return current_paintOpId; } void KisPaintOpPresetsPopup::setPresetImage(const QImage& image) { m_d->uiWdgPaintOpPresetSettings.scratchPad->setPresetImage(image); saveDialog->brushPresetThumbnailWidget->setPresetImage(image); } void KisPaintOpPresetsPopup::hideEvent(QHideEvent *event) { if (m_d->ignoreHideEvents) { return; } if (m_d->detached) { m_d->detachedGeometry = window()->geometry(); } QWidget::hideEvent(event); } void KisPaintOpPresetsPopup::showEvent(QShowEvent *) { if (m_d->detached) { window()->setGeometry(m_d->detachedGeometry); } emit brushEditorShown(); } void KisPaintOpPresetsPopup::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); emit sizeChanged(); } bool KisPaintOpPresetsPopup::detached() const { return m_d->detached; } void KisPaintOpPresetsPopup::slotSwitchScratchpad(bool visible) { // hide all the internal controls except the toggle button m_d->uiWdgPaintOpPresetSettings.scratchPad->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillGradient->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillLayer->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillSolid->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.scratchpadSidebarLabel->setVisible(visible); if (visible) { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } else { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } KisConfig cfg; cfg.setScratchpadVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowEditor(bool visible) { m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowPresets(bool visible) { m_d->uiWdgPaintOpPresetSettings.presetWidget->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.engineFilterLabel->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.presetsSidebarLabel->setVisible(visible); // we only want a spacer to work when the toggle icon is present. Otherwise the list of presets will shrink // which is something we don't want if (visible) { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::Ignored,QSizePolicy::Ignored); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } else { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } } void KisPaintOpPresetsPopup::slotUpdatePaintOpFilter() { QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data QString filterPaintOpId = userData.toString(); if (filterPaintOpId == "all_options") { filterPaintOpId = ""; } m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(filterPaintOpId); } void KisPaintOpPresetsPopup::slotSaveBrushPreset() { // here we are assuming that people want to keep their existing preset icon. We will just update the // settings and save a new copy with the same name. // there is a dialog with save options, but we don't need to show it in this situation - saveDialog->isSavingNewBrush(false); // this mostly just makes sure we keep the existing brush preset name when saving + saveDialog->useNewBrushDialog(false); // this mostly just makes sure we keep the existing brush preset name when saving saveDialog->loadExistingThumbnail(); // This makes sure we use the existing preset icon when updating the existing brush preset saveDialog->savePreset(); // refresh the view settings so the brush doesn't appear dirty slotUpdatePresetSettings(); } void KisPaintOpPresetsPopup::slotSaveNewBrushPreset() { - saveDialog->isSavingNewBrush(true); + saveDialog->useNewBrushDialog(true); saveDialog->saveScratchPadThumbnailArea(m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay()); saveDialog->showDialog(); } void KisPaintOpPresetsPopup::updateViewSettings() { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->updateViewSettings(); } void KisPaintOpPresetsPopup::currentPresetChanged(KisPaintOpPresetSP preset) { if (preset) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(preset.data()); setCurrentPaintOpId(preset->paintOp().id()); } } void KisPaintOpPresetsPopup::updateThemedIcons() { m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); } void KisPaintOpPresetsPopup::slotUpdatePresetSettings() { if (!m_d->resourceProvider) { return; } if (!m_d->resourceProvider->currentPreset()) { return; } bool isPresetDirty = m_d->resourceProvider->currentPreset()->isPresetDirty(); // don't need to reload or overwrite a clean preset m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setVisible(isPresetDirty); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(isPresetDirty); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(isPresetDirty); // update live preview area in here... // don't update the live preview if the widget is not visible. if (m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->isVisible()) { m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setCurrentPreset(m_d->resourceProvider->currentPreset()); m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->updateStroke(); } } diff --git a/libs/ui/widgets/kis_paintop_presets_save.cpp b/libs/ui/widgets/kis_paintop_presets_save.cpp index 28be44bf56..98abdb9c98 100644 --- a/libs/ui/widgets/kis_paintop_presets_save.cpp +++ b/libs/ui/widgets/kis_paintop_presets_save.cpp @@ -1,254 +1,268 @@ /* This file is part of the KDE project * Copyright (C) 2017 Scott Petrovic * * 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 "widgets/kis_paintop_presets_save.h" #include #include #include #include #include #include #include "KisImportExportManager.h" #include "QDesktopServices" #include "kis_resource_server_provider.h" #include KisPresetSaveWidget::KisPresetSaveWidget(QWidget * parent) : KisPaintOpPresetSaveDialog(parent) { // this is setting the area we will "capture" for saving the brush preset. It can potentially be a different // area that the entire scratchpad brushPresetThumbnailWidget->setCutoutOverlayRect(QRect(0, 0, brushPresetThumbnailWidget->height(), brushPresetThumbnailWidget->width())); // we will default to reusing the previous preset thumbnail // have that checked by default, hide the other elements, and load the last preset image connect(clearBrushPresetThumbnailButton, SIGNAL(clicked(bool)), brushPresetThumbnailWidget, SLOT(fillDefault())); connect(loadImageIntoThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadImageFromFile())); connect(loadScratchPadThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadScratchpadThumbnail())); connect(loadExistingThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadExistingThumbnail())); connect(loadIconLibraryThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadImageFromLibrary())); connect(savePresetButton, SIGNAL(clicked(bool)), this, SLOT(savePreset())); connect(cancelButton, SIGNAL(clicked(bool)), this, SLOT(close())); } KisPresetSaveWidget::~KisPresetSaveWidget() { } void KisPresetSaveWidget::scratchPadSetup(KisCanvasResourceProvider* resourceProvider) { m_resourceProvider = resourceProvider; brushPresetThumbnailWidget->setupScratchPad(m_resourceProvider, Qt::white); } void KisPresetSaveWidget::showDialog() { setModal(true); // set the name of the current brush preset area. KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); // UI will look a bit different if we are saving a new brush - if (m_isSavingNewBrush) { + if (m_useNewBrushDialog) { setWindowTitle(i18n("Save New Brush Preset")); newBrushNameTexField->setVisible(true); clearBrushPresetThumbnailButton->setVisible(true); loadImageIntoThumbnailButton->setVisible(true); currentBrushNameLabel->setVisible(false); if (preset) { newBrushNameTexField->setText(preset->name().append(" ").append(i18n("Copy"))); } } else { setWindowTitle(i18n("Save Brush Preset")); if (preset) { currentBrushNameLabel->setText(preset->name()); } newBrushNameTexField->setVisible(false); currentBrushNameLabel->setVisible(true); } brushPresetThumbnailWidget->paintPresetImage(); show(); } void KisPresetSaveWidget::loadImageFromFile() { // create a dialog to retrieve an image file. KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); QString filename = dialog.filename(); // the filename() returns the entire path & file name, not just the file name if (filename != "") { // empty if "cancel" is pressed // take that file and load it into the thumbnail are const QImage imageToLoad(filename); brushPresetThumbnailWidget->fillTransparent(); // clear the background in case our new image has transparency brushPresetThumbnailWidget->paintCustomImage(imageToLoad); } } void KisPresetSaveWidget::loadScratchpadThumbnail() { brushPresetThumbnailWidget->paintCustomImage(scratchPadThumbnailArea); } void KisPresetSaveWidget::loadExistingThumbnail() { brushPresetThumbnailWidget->paintPresetImage(); } void KisPresetSaveWidget::loadImageFromLibrary() { //add dialog code here. QDialog *dlg = new QDialog(this); dlg->setWindowTitle(i18n("Preset Icon Library")); QVBoxLayout *layout = new QVBoxLayout(); dlg->setLayout(layout); KisPaintopPresetIconLibrary *libWidget = new KisPaintopPresetIconLibrary(dlg); layout->addWidget(libWidget); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, dlg); connect(buttons, SIGNAL(accepted()), dlg, SLOT(accept())); connect(buttons, SIGNAL(rejected()), dlg, SLOT(reject())); layout->addWidget(buttons); //if dialog accepted, get image. if (dlg->exec()==QDialog::Accepted) { QImage presetImage = libWidget->getImage(); brushPresetThumbnailWidget->paintCustomImage(presetImage); } } void KisPresetSaveWidget::setFavoriteResourceManager(KisFavoriteResourceManager * favManager) { m_favoriteResourceManager = favManager; } void KisPresetSaveWidget::savePreset() { KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset(); if (!curPreset) return; m_favoriteResourceManager->setBlockUpdates(true); - KisPaintOpPresetSP oldPreset = curPreset->clone(); + KisPaintOpPresetSP oldPreset = curPreset->clone(); // tags are not cloned with this oldPreset->load(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QString saveLocation = rServer->saveLocation(); // if we are saving a new brush, use what we type in for the input - QString presetName = m_isSavingNewBrush ? newBrushNameTexField->text() : curPreset->name(); - + QString presetName = m_useNewBrushDialog ? newBrushNameTexField->text() : curPreset->name(); QString currentPresetFileName = saveLocation + presetName + curPreset->defaultFileExtension(); + bool isSavingOverExistingPreset = rServer->resourceByName(presetName); - // if the preset already exists, make a back up of it - if (rServer->resourceByName(presetName)) { + // make a back up of the existing preset if we are saving over it + if (isSavingOverExistingPreset) { QString currentDate = QDate::currentDate().toString(Qt::ISODate); QString currentTime = QTime::currentTime().toString(Qt::ISODate); QString presetFilename = saveLocation + presetName + "_backup_" + currentDate + "-" + currentTime + oldPreset->defaultFileExtension(); oldPreset->setFilename(presetFilename); oldPreset->setName(presetName); oldPreset->setPresetDirty(false); oldPreset->setValid(true); - // add resource to the blacklist + // add backup resource to the blacklist rServer->addResource(oldPreset); rServer->removeResourceAndBlacklist(oldPreset.data()); + QStringList tags; tags = rServer->assignedTagsList(curPreset.data()); Q_FOREACH (const QString & tag, tags) { rServer->addTag(oldPreset.data(), tag); } } - if (m_isSavingNewBrush) { + + if (m_useNewBrushDialog) { KisPaintOpPresetSP newPreset = curPreset->clone(); newPreset->setFilename(currentPresetFileName); newPreset->setName(presetName); newPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay()); newPreset->setPresetDirty(false); newPreset->setValid(true); + + // keep tags if we are saving over existing preset + if (isSavingOverExistingPreset) { + QStringList tags; + tags = rServer->assignedTagsList(curPreset.data()); + Q_FOREACH (const QString & tag, tags) { + rServer->addTag(newPreset.data(), tag); + } + } + rServer->addResource(newPreset); // trying to get brush preset to load after it is created emit resourceSelected(newPreset.data()); + } + else { // saving a preset that is replacing an existing one - } else { - - if (curPreset->filename().contains(saveLocation)==false || curPreset->filename().contains(presetName)==false) { + if (curPreset->filename().contains(saveLocation) == false || curPreset->filename().contains(presetName) == false) { rServer->removeResourceAndBlacklist(curPreset.data()); curPreset->setFilename(currentPresetFileName); curPreset->setName(presetName); } if (!rServer->resourceByFilename(curPreset->filename())){ //this is necessary so that we can get the preset afterwards. rServer->addResource(curPreset, false, false); rServer->removeFromBlacklist(curPreset.data()); } curPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay()); curPreset->save(); curPreset->load(); } // HACK ALERT! the server does not notify the observers // automatically, so we need to call theupdate manually! rServer->tagCategoryMembersChanged(); m_favoriteResourceManager->setBlockUpdates(false); close(); // we are done... so close the save brush dialog } + + void KisPresetSaveWidget::saveScratchPadThumbnailArea(QImage image) { scratchPadThumbnailArea = image; } -void KisPresetSaveWidget::isSavingNewBrush(bool newBrush) +void KisPresetSaveWidget::useNewBrushDialog(bool show) { - m_isSavingNewBrush = newBrush; + m_useNewBrushDialog = show; } #include "moc_kis_paintop_presets_save.cpp" diff --git a/libs/ui/widgets/kis_paintop_presets_save.h b/libs/ui/widgets/kis_paintop_presets_save.h index a334a1954c..3185a8b478 100644 --- a/libs/ui/widgets/kis_paintop_presets_save.h +++ b/libs/ui/widgets/kis_paintop_presets_save.h @@ -1,80 +1,81 @@ /* This file is part of the KDE project * Copyright (C) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_PRESETS_SAVE_H #define KIS_PAINTOP_PRESETS_SAVE_H #include #include #include "ui_wdgsavebrushpreset.h" #include "kis_canvas_resource_provider.h" #include "kis_favorite_resource_manager.h" class KisPaintOpPresetSaveDialog : public QDialog , public Ui::WdgSaveBrushPreset { Q_OBJECT public: KisPaintOpPresetSaveDialog(QWidget* parent) : QDialog(parent) { setupUi(this); } }; class KisPresetSaveWidget : public KisPaintOpPresetSaveDialog { Q_OBJECT public: KisPresetSaveWidget(QWidget* parent); virtual ~KisPresetSaveWidget(); void showDialog(); - void isSavingNewBrush(bool newBrush); + /// determines if we should show the save as dialog (true) or save in the background (false) + void useNewBrushDialog(bool show); void scratchPadSetup(KisCanvasResourceProvider* resourceProvider); void saveScratchPadThumbnailArea(const QImage image); KisCanvasResourceProvider* m_resourceProvider; void setFavoriteResourceManager(KisFavoriteResourceManager * favManager); Q_SIGNALS: void resourceSelected(KoResource* resource); public Q_SLOTS: void loadImageFromFile(); void savePreset(); void loadScratchpadThumbnail(); void loadExistingThumbnail(); void loadImageFromLibrary(); private: - bool m_isSavingNewBrush; + bool m_useNewBrushDialog; KisFavoriteResourceManager * m_favoriteResourceManager; QImage scratchPadThumbnailArea; }; #endif diff --git a/libs/ui/widgets/kis_preset_chooser.cpp b/libs/ui/widgets/kis_preset_chooser.cpp index 34965f5d9a..0674228837 100644 --- a/libs/ui/widgets/kis_preset_chooser.cpp +++ b/libs/ui/widgets/kis_preset_chooser.cpp @@ -1,361 +1,363 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * * 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_preset_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceItemView.h" #include #include #include "kis_resource_server_provider.h" #include "kis_global.h" #include "kis_slider_spin_box.h" #include "kis_config.h" #include "kis_config_notifier.h" #include /// The resource item delegate for rendering the resource preview class KisPresetDelegate : public QAbstractItemDelegate { public: KisPresetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent), m_showText(false), m_useDirtyPresets(false) {} ~KisPresetDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } void setShowText(bool showText) { m_showText = showText; } void setUseDirtyPresets(bool value) { m_useDirtyPresets = value; } private: bool m_showText; bool m_useDirtyPresets; }; void KisPresetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if (! index.isValid()) return; KisPaintOpPreset* preset = static_cast(index.internalPointer()); QImage preview = preset->image(); if(preview.isNull()) { return; } QRect paintRect = option.rect.adjusted(1, 1, -1, -1); if (!m_showText) { painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { QSize pixSize(paintRect.height(), paintRect.height()); painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); // Put an asterisk after the preset if it is dirty. This will help in case the pixmap icon is too small QString dirtyPresetIndicator = QString(""); if (m_useDirtyPresets && preset->isPresetDirty()) { dirtyPresetIndicator = QString("*"); } qreal brushSize = preset->settings()->paintOpSize(); QString brushSizeText; // Disable displayed decimal precision beyond a certain brush size if (brushSize < 100) { brushSizeText = QString::number(brushSize, 'g', 3); } else { brushSizeText = QString::number(brushSize, 'f', 0); } - painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, brushSizeText); - painter->drawText(pixSize.width() + 40, option.rect.y() + option.rect.height() - 10, preset->name().append(dirtyPresetIndicator)); + painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, brushSizeText); // brush size + + QString presetDisplayName = preset->name().replace("_", " "); // don't need underscores that might be part of the file name + painter->drawText(pixSize.width() + 40, option.rect.y() + option.rect.height() - 10, presetDisplayName.append(dirtyPresetIndicator)); } if (m_useDirtyPresets && preset->isPresetDirty()) { const QIcon icon = KisIconUtils::loadIcon(koIconName("dirty-preset")); QPixmap pixmap = icon.pixmap(QSize(15,15)); painter->drawPixmap(paintRect.x() + 3, paintRect.y() + 3, pixmap); } if (!preset->settings() || !preset->settings()->isValid()) { const QIcon icon = KisIconUtils::loadIcon("broken-preset"); icon.paint(painter, QRect(paintRect.x() + paintRect.height() - 25, paintRect.y() + paintRect.height() - 25, 25, 25)); } if (option.state & QStyle::State_Selected) { painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(1.0); painter->fillRect(option.rect, option.palette.highlight()); // highlight is not strong enough to pick out preset. draw border around it. painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setPen(QPen(option.palette.highlight(), 4, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); QRect selectedBorder = option.rect.adjusted(2 , 2, -2, -2); // constrict the rectangle so it doesn't bleed into other presets painter->drawRect(selectedBorder); } painter->restore(); } class KisPresetProxyAdapter : public KisPaintOpPresetResourceServerAdapter { public: KisPresetProxyAdapter(KisPaintOpPresetResourceServer* resourceServer) : KisPaintOpPresetResourceServerAdapter(resourceServer) { setSortingEnabled(true); } ~KisPresetProxyAdapter() override {} QList< KoResource* > resources() override { QList serverResources = KisPaintOpPresetResourceServerAdapter::resources(); if (m_paintopID.isEmpty()) { return serverResources; } QList resources; Q_FOREACH (KoResource *resource, serverResources) { KisPaintOpPreset *preset = dynamic_cast(resource); if (preset && preset->paintOp().id() == m_paintopID) { resources.append(preset); } } return resources; } ///Set id for paintop to be accept by the proxy model, if not filter is set all ///presets will be shown. void setPresetFilter(const QString& paintOpId) { m_paintopID = paintOpId; invalidate(); } ///Resets the model connected to the adapter void invalidate() { emitRemovingResource(0); } QString currentPaintOpId() const { return m_paintopID; } private: QString m_paintopID; }; KisPresetChooser::KisPresetChooser(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QVBoxLayout * layout = new QVBoxLayout(this); layout->setMargin(0); KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false); m_adapter = QSharedPointer(new KisPresetProxyAdapter(rserver)); m_chooser = new KoResourceItemChooser(m_adapter, this); m_chooser->setObjectName("ResourceChooser"); m_chooser->setColumnCount(10); m_chooser->setRowHeight(50); m_delegate = new KisPresetDelegate(this); m_chooser->setItemDelegate(m_delegate); m_chooser->setSynced(true); layout->addWidget(m_chooser); KisConfig cfg; m_chooser->configureKineticScrolling(cfg.kineticScrollingGesture(), cfg.kineticScrollingSensitivity(), cfg.kineticScrollingScrollbar()); connect(m_chooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(resourceSelected(KoResource*))); connect(m_chooser, SIGNAL(resourceClicked(KoResource*)), this, SIGNAL(resourceClicked(KoResource*))); m_mode = THUMBNAIL; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(notifyConfigChanged())); notifyConfigChanged(); } KisPresetChooser::~KisPresetChooser() { } void KisPresetChooser::showButtons(bool show) { m_chooser->showButtons(show); } void KisPresetChooser::setViewMode(KisPresetChooser::ViewMode mode) { m_mode = mode; updateViewSettings(); } void KisPresetChooser::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); updateViewSettings(); } void KisPresetChooser::notifyConfigChanged() { KisConfig cfg; m_delegate->setUseDirtyPresets(cfg.useDirtyPresets()); setIconSize(cfg.presetIconSize()); updateViewSettings(); } void KisPresetChooser::updateViewSettings() { if (m_mode == THUMBNAIL) { m_chooser->setSynced(true); m_delegate->setShowText(false); } else if (m_mode == DETAIL) { m_chooser->setSynced(false); m_chooser->setColumnCount(1); m_chooser->setColumnWidth(m_chooser->width()); KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); m_chooser->setRowHeight(chooserSync->baseLength()); m_delegate->setShowText(true); } else if (m_mode == STRIP) { m_chooser->setSynced(false); m_chooser->setRowCount(1); m_chooser->itemView()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_chooser->itemView()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // An offset of 7 keeps the cell exactly square, TODO: use constants, not hardcoded numbers m_chooser->setColumnWidth(m_chooser->viewSize().height() - 7); m_delegate->setShowText(false); } } void KisPresetChooser::setCurrentResource(KoResource *resource) { /** * HACK ALERT: here we use a direct call to an adapter to notify the view * that the preset might have changed its dirty state. This state * doesn't affect the filtering so the server's cache must not be * invalidated! * * Ideally, we should call some method of KoResourceServer instead, * but ut seems like a bit too much effort for such a small fix. */ if (resource == currentResource()) { KisPresetProxyAdapter *adapter = static_cast(m_adapter.data()); KisPaintOpPreset *preset = dynamic_cast(resource); if (preset) { adapter->resourceChangedNoCacheInvalidation(preset); } } m_chooser->setCurrentResource(resource); } KoResource* KisPresetChooser::currentResource() const { return m_chooser->currentResource(); } void KisPresetChooser::showTaggingBar(bool show) { m_chooser->showTaggingBar(show); } KoResourceItemChooser *KisPresetChooser::itemChooser() { return m_chooser; } void KisPresetChooser::setPresetFilter(const QString& paintOpId) { KisPresetProxyAdapter *adapter = static_cast(m_adapter.data()); if (adapter->currentPaintOpId() != paintOpId) { adapter->setPresetFilter(paintOpId); updateViewSettings(); } } void KisPresetChooser::setIconSize(int newSize) { KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); chooserSync->setBaseLength(newSize); updateViewSettings(); } int KisPresetChooser::iconSize() { KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); return chooserSync->baseLength(); } void KisPresetChooser::saveIconSize() { // save icon size KisConfig cfg; cfg.setPresetIconSize(iconSize()); } diff --git a/libs/widgets/KoRuler.cpp b/libs/widgets/KoRuler.cpp index f7bfc617cf..aaa632c21b 100644 --- a/libs/widgets/KoRuler.cpp +++ b/libs/widgets/KoRuler.cpp @@ -1,1370 +1,1381 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (C) 2006 Peter Simonsson Copyright (C) 2007 C. Boemann Copyright (C) 2007-2008 Jan Hambrecht Copyright (C) 2007 Thomas Zander 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 "KoRuler.h" #include "KoRuler_p.h" #include #include #include #include #include #include #include #include #include #include // the distance in pixels of a mouse position considered outside the rule static const int OutsideRulerThreshold = 20; // static const int fullStepMarkerLength = 6; static const int halfStepMarkerLength = 6; static const int quarterStepMarkerLength = 3; static const int measurementTextAboveBelowMargin = 1; void RulerTabChooser::mousePressEvent(QMouseEvent *) { if (! m_showTabs) { return; } switch(m_type) { case QTextOption::LeftTab: m_type = QTextOption::RightTab; break; case QTextOption::RightTab: m_type = QTextOption::CenterTab; break; case QTextOption::CenterTab: m_type = QTextOption::DelimiterTab; break; case QTextOption::DelimiterTab: m_type = QTextOption::LeftTab; break; } update(); } void RulerTabChooser::paintEvent(QPaintEvent *) { if (! m_showTabs) { return; } QPainter painter(this); QPolygonF polygon; painter.setPen(QPen(palette().color(QPalette::Text), 0)); painter.setBrush(palette().color(QPalette::Text)); painter.setRenderHint( QPainter::Antialiasing ); qreal x= width()/2; painter.translate(0,-height()/2+5); switch (m_type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x+6.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, height() - 2.5) << QPointF(x+0.5, height() - 8.5); painter.drawPolyline(polygon); break; default: break; } } struct { bool operator()(KoRuler::Tab tab1, KoRuler::Tab tab2) const { return tab1.position < tab2.position; } } compareTabs; QRectF HorizontalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewX(d->rulerLength); QRectF rectangle; rectangle.setX(qMax(0, d->offset)); rectangle.setY(0); rectangle.setWidth(qMin(qreal(d->ruler->width() - 1.0 - rectangle.x()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); rectangle.setHeight(d->ruler->height() - 1); QRectF activeRangeRectangle; activeRangeRectangle.setX(qMax(rectangle.x() + 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setY(rectangle.y() + 1); activeRangeRectangle.setRight(qMin(rectangle.right() - 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()) + d->offset)); activeRangeRectangle.setHeight(rectangle.height() - 2); painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); // make background slightly different so it is easier to see painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } } return rectangle; } void HorizontalPaintingStrategy::drawTabs(const KoRulerPrivate *d, QPainter &painter) { if (! d->showTabs) return; QPolygonF polygon; const QColor tabColor = d->ruler->palette().color(QPalette::Text); painter.setPen(QPen(tabColor, 0)); painter.setBrush(tabColor); painter.setRenderHint( QPainter::Antialiasing ); qreal position = -10000; foreach (const KoRuler::Tab & t, d->tabs) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } position = qMax(position, t.position); polygon.clear(); switch (t.type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x+6.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 6.5); painter.drawPolyline(polygon); break; default: break; } } // and also draw the regular interval tab that are non editable if (d->tabDistance > 0.0) { // first possible position position = qMax(position, d->relativeTabs ? 0 : d->paragraphIndent); if (position < 0) { position = int(position / d->tabDistance) * d->tabDistance; } else { position = (int(position / d->tabDistance) + 1) * d->tabDistance; } while (position < d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart() - d->endIndent) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + position) + d->offset; } polygon.clear(); polygon << QPointF(x+0.5, d->ruler->height() - 3.5) << QPointF(x+4.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); position += d->tabDistance; } } } void HorizontalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit // QRectF activeRangeRectangle; int numberStepPixel = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(numberStep))); // const bool adjustMillimeters = (d->unit.type() == KoUnit::Millimeter); const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); if (numberStepPixel == 0 || numberStep == 0) return; // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding // Change number step so all digits fits while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } int start=0; // Calc the first number step if(d->offset < 0) start = qAbs(d->offset); // make a little hack so rulers shows correctly inversed number aligned const qreal lengthInUnit = d->unit.toUserValue(d->rulerLength); const qreal hackyLength = lengthInUnit - fmod(lengthInUnit, numberStep); if(d->rightToLeft) { start -= int(d->viewConverter->documentToViewX(fmod(d->rulerLength, d->unit.fromUserValue(numberStep)))); } int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; int pos = 0; const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(d->offset, 0); const int len = qRound(rectangle.width()) + start; int nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - fullStepMarkerLength)); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); int x = pos; if (d->rightToLeft) { // this is done in a hacky way with the fine tuning done above numberText = QString::number(hackyLength - stepCount * numberStep); } painter.setPen(numberPen); painter.drawText(QPointF(x-fontMetrics.width(numberText)/2.0, rectangle.bottom() -fullStepMarkerLength -measurementTextAboveBelowMargin), numberText); painter.setPen(markerPen); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - halfStepMarkerLength)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - quarterStepMarkerLength)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal top = rectangle.y() + 1; const qreal bottom = rectangle.bottom() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.width() ) painter.drawLine(QPointF(mouseCoord, top), QPointF(mouseCoord, bottom)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal x = d->viewConverter->documentToViewX(hp.position) + d->offset; painter.drawLine(QPointF(x, top), QPointF(x, bottom)); } } } void HorizontalPaintingStrategy::drawIndents(const KoRulerPrivate *d, QPainter &painter) { QPolygonF polygon; painter.setBrush(d->ruler->palette().brush(QPalette::Base)); painter.setRenderHint( QPainter::Antialiasing ); qreal x; // Draw first line start indent if (d->rightToLeft) x = d->effectiveActiveRangeEnd() - d->firstLineIndent - d->paragraphIndent; else x = d->effectiveActiveRangeStart() + d->firstLineIndent + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); polygon << QPointF(x+6.5, 0.5) << QPointF(x+0.5, 8.5) << QPointF(x-5.5, 0.5) << QPointF(x+5.5, 0.5); painter.drawPolygon(polygon); // draw the hanging indent. if (d->rightToLeft) x = d->effectiveActiveRangeStart() + d->endIndent; else x = d->effectiveActiveRangeStart() + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); const int bottom = d->ruler->height(); polygon.clear(); polygon << QPointF(x+6.5, bottom - 0.5) << QPointF(x+0.5, bottom - 8.5) << QPointF(x-5.5, bottom - 0.5) << QPointF(x+5.5, bottom - 0.5); painter.drawPolygon(polygon); // Draw end-indent or paragraph indent if mode is rightToLeft qreal diff; if (d->rightToLeft) diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->paragraphIndent) + d->offset - x; else diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->endIndent) + d->offset - x; polygon.translate(diff, 0); painter.drawPolygon(polygon); } QSize HorizontalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(0, minimum); } QRectF VerticalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewY(d->rulerLength); QRectF rectangle; rectangle.setX(0); rectangle.setY(qMax(0, d->offset)); rectangle.setWidth(d->ruler->width() - 1.0); rectangle.setHeight(qMin(qreal(d->ruler->height() - 1.0 - rectangle.y()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); QRectF activeRangeRectangle; activeRangeRectangle.setX(rectangle.x() + 1); activeRangeRectangle.setY(qMax(rectangle.y() + 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setWidth(rectangle.width() - 2); activeRangeRectangle.setBottom(qMin(rectangle.bottom() - 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeEnd()) + d->offset)); painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } } return rectangle; } void VerticalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit int numberStepPixel = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep))); if (numberStepPixel <= 0) return; const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding if (numberStepPixel == 0 || numberStep == 0) return; // Change number step so all digits will fit while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } // Calc the first number step const int start = d->offset < 0 ? qAbs(d->offset) : 0; // make a little hack so rulers shows correctly inversed number aligned int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(0, d->offset); const int len = qRound(rectangle.height()) + start; int nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); int pos = 0; for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { painter.save(); painter.translate(rectangle.right()-fullStepMarkerLength, pos); if(pos != 0) painter.drawLine(QPointF(0, 0), QPointF(fullStepMarkerLength-1, 0)); painter.rotate(-90); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); painter.setPen(numberPen); painter.drawText(QPointF(-fontMetrics.width(numberText) / 2.0, -measurementTextAboveBelowMargin), numberText); painter.restore(); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - halfStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - quarterStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal left = rectangle.left() + 1; const qreal right = rectangle.right() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.height() ) painter.drawLine(QPointF(left, mouseCoord), QPointF(right, mouseCoord)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal y = d->viewConverter->documentToViewY(hp.position) + d->offset; painter.drawLine(QPointF(left, y), QPointF(right, y)); } } } QSize VerticalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(minimum, 0); } void HorizontalDistancesPaintingStrategy::drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end) { // Don't draw too short lines if (qMax(start, end) - qMin(start, end) < 1) return; painter.save(); painter.translate(d->offset, d->ruler->height() / 2); painter.setPen(QPen(d->ruler->palette().color(QPalette::Text), 0)); painter.setBrush(d->ruler->palette().color(QPalette::Text)); QLineF line(QPointF(d->viewConverter->documentToViewX(start), 0), QPointF(d->viewConverter->documentToViewX(end), 0)); QPointF midPoint = line.pointAt(0.5); // Draw the label text const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); QString label = d->unit.toUserStringValue( d->viewConverter->viewToDocumentX(line.length())) + ' ' + d->unit.symbol(); QPointF labelPosition = QPointF(midPoint.x() - fontMetrics.width(label)/2, midPoint.y() + fontMetrics.ascent()/2); painter.setFont(font); painter.drawText(labelPosition, label); // Draw the arrow lines qreal arrowLength = (line.length() - fontMetrics.width(label)) / 2 - 2; arrowLength = qMax(qreal(0.0), arrowLength); QLineF startArrow(line.p1(), line.pointAt(arrowLength / line.length())); QLineF endArrow(line.p2(), line.pointAt(1.0 - arrowLength / line.length())); painter.drawLine(startArrow); painter.drawLine(endArrow); // Draw the arrow heads QPolygonF arrowHead; arrowHead << line.p1() << QPointF(line.x1()+3, line.y1()-3) << QPointF(line.x1()+3, line.y1()+3); painter.drawPolygon(arrowHead); arrowHead.clear(); arrowHead << line.p2() << QPointF(line.x2()-3, line.y2()-3) << QPointF(line.x2()-3, line.y2()+3); painter.drawPolygon(arrowHead); painter.restore(); } void HorizontalDistancesPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF&) { QList points; points << 0.0; points << d->effectiveActiveRangeStart() + d->paragraphIndent + d->firstLineIndent; points << d->effectiveActiveRangeStart() + d->paragraphIndent; points << d->effectiveActiveRangeEnd() - d->endIndent; points << d->effectiveActiveRangeStart(); points << d->effectiveActiveRangeEnd(); points << d->rulerLength; std::sort(points.begin(), points.end()); QListIterator i(points); i.next(); while (i.hasNext() && i.hasPrevious()) { drawDistanceLine(d, painter, i.peekPrevious(), i.peekNext()); i.next(); } } KoRulerPrivate::KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation o) : unit(KoUnit(KoUnit::Point)), orientation(o), viewConverter(vc), offset(0), rulerLength(0), activeRangeStart(0), activeRangeEnd(0), activeOverrideRangeStart(0), activeOverrideRangeEnd(0), mouseCoordinate(-1), showMousePosition(0), showSelectionBorders(false), firstSelectionBorder(0), secondSelectionBorder(0), showIndents(false), firstLineIndent(0), paragraphIndent(0), endIndent(0), showTabs(false), relativeTabs(false), tabMoved(false), originalIndex(-1), currentIndex(0), rightToLeft(false), selected(None), selectOffset(0), tabChooser(0), normalPaintingStrategy(o == Qt::Horizontal ? (PaintingStrategy*)new HorizontalPaintingStrategy() : (PaintingStrategy*)new VerticalPaintingStrategy()), distancesPaintingStrategy((PaintingStrategy*)new HorizontalDistancesPaintingStrategy()), paintingStrategy(normalPaintingStrategy), ruler(parent), - guideCreationStarted(false) + guideCreationStarted(false), + pixelStep(100.0) { } KoRulerPrivate::~KoRulerPrivate() { delete normalPaintingStrategy; delete distancesPaintingStrategy; } qreal KoRulerPrivate::numberStepForUnit() const { switch(unit.type()) { case KoUnit::Inch: case KoUnit::Centimeter: case KoUnit::Decimeter: case KoUnit::Millimeter: return 1.0; case KoUnit::Pica: case KoUnit::Cicero: return 10.0; case KoUnit::Point: default: - return 100.0; + return pixelStep; } } qreal KoRulerPrivate::doSnapping(const qreal value) const { qreal numberStep = unit.fromUserValue(numberStepForUnit()/4.0); return numberStep * qRound(value / numberStep); } KoRulerPrivate::Selection KoRulerPrivate::selectionAtPosition(const QPoint & pos, int *selectOffset ) { const int height = ruler->height(); if (rightToLeft) { int x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - firstLineIndent - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() > height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } else { int x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + firstLineIndent + paragraphIndent) + offset); if (pos.x() >= x -8 && pos.x() <= x + 8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8 && pos.y() > height/2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } return KoRulerPrivate::None; } int KoRulerPrivate::hotSpotIndex(const QPoint & pos) { for(int counter = 0; counter < hotspots.count(); counter++) { bool hit; if (orientation == Qt::Horizontal) hit = qAbs(viewConverter->documentToViewX(hotspots[counter].position) - pos.x() + offset) < 3; else hit = qAbs(viewConverter->documentToViewY(hotspots[counter].position) - pos.y() + offset) < 3; if (hit) return counter; } return -1; } qreal KoRulerPrivate::effectiveActiveRangeStart() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeStart; } else { return activeRangeStart; } } qreal KoRulerPrivate::effectiveActiveRangeEnd() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeEnd; } else { return activeRangeEnd; } } void KoRulerPrivate::emitTabChanged() { KoRuler::Tab tab; if (currentIndex >= 0) tab = tabs[currentIndex]; emit ruler->tabChanged(originalIndex, currentIndex >= 0 ? &tab : 0); } KoRuler::KoRuler(QWidget* parent, Qt::Orientation orientation, const KoViewConverter* viewConverter) : QWidget(parent) , d( new KoRulerPrivate( this, viewConverter, orientation) ) { setMouseTracking( true ); } KoRuler::~KoRuler() { delete d; } KoUnit KoRuler::unit() const { return d->unit; } void KoRuler::setUnit(const KoUnit &unit) { d->unit = unit; update(); } qreal KoRuler::rulerLength() const { return d->rulerLength; } Qt::Orientation KoRuler::orientation() const { return d->orientation; } void KoRuler::setOffset(int offset) { d->offset = offset; update(); } void KoRuler::setRulerLength(qreal length) { d->rulerLength = length; update(); } void KoRuler::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setClipRegion(event->region()); painter.save(); QRectF rectangle = d->paintingStrategy->drawBackground(d, painter); painter.restore(); painter.save(); d->paintingStrategy->drawMeasurements(d, painter, rectangle); painter.restore(); if (d->showIndents) { painter.save(); d->paintingStrategy->drawIndents(d, painter); painter.restore(); } d->paintingStrategy->drawTabs(d, painter); } QSize KoRuler::minimumSizeHint() const { return d->paintingStrategy->sizeHint(); } QSize KoRuler::sizeHint() const { return d->paintingStrategy->sizeHint(); } void KoRuler::setActiveRange(qreal start, qreal end) { d->activeRangeStart = start; d->activeRangeEnd = end; update(); } void KoRuler::setOverrideActiveRange(qreal start, qreal end) { d->activeOverrideRangeStart = start; d->activeOverrideRangeEnd = end; update(); } void KoRuler::updateMouseCoordinate(int coordinate) { if(d->mouseCoordinate == coordinate) return; d->mouseCoordinate = coordinate; update(); } void KoRuler::setShowMousePosition(bool show) { d->showMousePosition = show; update(); } bool KoRuler::showMousePosition() const { return d->showMousePosition; } void KoRuler::setRightToLeft(bool isRightToLeft) { d->rightToLeft = isRightToLeft; update(); } void KoRuler::setShowIndents(bool show) { d->showIndents = show; update(); } void KoRuler::setFirstLineIndent(qreal indent) { d->firstLineIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setParagraphIndent(qreal indent) { d->paragraphIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setEndIndent(qreal indent) { d->endIndent = indent; if (d->showIndents) { update(); } } qreal KoRuler::firstLineIndent() const { return d->firstLineIndent; } qreal KoRuler::paragraphIndent() const { return d->paragraphIndent; } qreal KoRuler::endIndent() const { return d->endIndent; } QWidget *KoRuler::tabChooser() { if ((d->tabChooser == 0) && (d->orientation == Qt::Horizontal)) { d->tabChooser = new RulerTabChooser(parentWidget()); d->tabChooser->setShowTabs(d->showTabs); } return d->tabChooser; } void KoRuler::setShowSelectionBorders(bool show) { d->showSelectionBorders = show; update(); } void KoRuler::updateSelectionBorders(qreal first, qreal second) { d->firstSelectionBorder = first; d->secondSelectionBorder = second; if(d->showSelectionBorders) update(); } void KoRuler::setShowTabs(bool show) { if (d->showTabs == show) { return; } d->showTabs = show; if (d->tabChooser) { d->tabChooser->setShowTabs(show); } update(); } void KoRuler::setRelativeTabs(bool relative) { d->relativeTabs = relative; if (d->showTabs) { update(); } } void KoRuler::updateTabs(const QList &tabs, qreal tabDistance) { d->tabs = tabs; d->tabDistance = tabDistance; if (d->showTabs) { update(); } } QList KoRuler::tabs() const { QList answer = d->tabs; std::sort(answer.begin(), answer.end(), compareTabs); return answer; } void KoRuler::setPopupActionList(const QList &popupActionList) { d->popupActions = popupActionList; } QList KoRuler::popupActionList() const { return d->popupActions; } void KoRuler::mousePressEvent ( QMouseEvent* ev ) { d->tabMoved = false; d->selected = KoRulerPrivate::None; if (ev->button() == Qt::RightButton && !d->popupActions.isEmpty()) QMenu::exec(d->popupActions, ev->globalPos()); if (ev->button() != Qt::LeftButton) { ev->ignore(); return; } /** * HACK ALERT: We don't need all that indentation stuff in Krita. * Just ensure the rulers are created correctly. */ if (d->selected == KoRulerPrivate::None) { d->guideCreationStarted = true; return; } QPoint pos = ev->pos(); if (d->showTabs) { int i = 0; int x; foreach (const Tab & t, d->tabs) { if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } if (pos.x() >= x-6 && pos.x() <= x+6) { d->selected = KoRulerPrivate::Tab; d->selectOffset = x - pos.x(); d->currentIndex = i; break; } i++; } d->originalIndex = d->currentIndex; } if (d->selected == KoRulerPrivate::None) d->selected = d->selectionAtPosition(ev->pos(), &d->selectOffset); if (d->selected == KoRulerPrivate::None) { int hotSpotIndex = d->hotSpotIndex(ev->pos()); if (hotSpotIndex >= 0) { d->selected = KoRulerPrivate::HotSpot; update(); } } if (d->showTabs && d->selected == KoRulerPrivate::None) { // still haven't found something so let assume the user wants to add a tab qreal tabpos; if (d->rightToLeft) { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) + d->effectiveActiveRangeEnd() + (d->relativeTabs ? d->paragraphIndent : 0); } else { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) - d->effectiveActiveRangeStart() - (d->relativeTabs ? d->paragraphIndent : 0); } Tab t = {tabpos, d->tabChooser ? d->tabChooser->type() : d->rightToLeft ? QTextOption::RightTab : QTextOption::LeftTab}; d->tabs.append(t); d->selectOffset = 0; d->selected = KoRulerPrivate::Tab; d->currentIndex = d->tabs.count() - 1; d->originalIndex = -1; // new! update(); } if (d->orientation == Qt::Horizontal && (ev->modifiers() & Qt::ShiftModifier) && (d->selected == KoRulerPrivate::FirstLineIndent || d->selected == KoRulerPrivate::ParagraphIndent || d->selected == KoRulerPrivate::Tab || d->selected == KoRulerPrivate::EndIndent)) d->paintingStrategy = d->distancesPaintingStrategy; if (d->selected != KoRulerPrivate::None) emit aboutToChange(); } void KoRuler::mouseReleaseEvent ( QMouseEvent* ev ) { ev->accept(); if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { d->guideCreationStarted = false; emit guideCreationFinished(d->orientation, ev->globalPos()); } else if (d->selected == KoRulerPrivate::Tab) { if (d->originalIndex >= 0 && !d->tabMoved) { int type = d->tabs[d->currentIndex].type; type++; if (type > 3) type = 0; d->tabs[d->currentIndex].type = static_cast (type); update(); } d->emitTabChanged(); } else if( d->selected != KoRulerPrivate::None) emit indentsChanged(true); else ev->ignore(); d->paintingStrategy = d->normalPaintingStrategy; d->selected = KoRulerPrivate::None; } void KoRuler::mouseMoveEvent ( QMouseEvent* ev ) { QPoint pos = ev->pos(); if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { emit guideCreationInProgress(d->orientation, ev->globalPos()); ev->accept(); update(); return; } qreal activeLength = d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart(); switch (d->selected) { case KoRulerPrivate::FirstLineIndent: if (d->rightToLeft) d->firstLineIndent = d->effectiveActiveRangeEnd() - d->paragraphIndent - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->firstLineIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart() - d->paragraphIndent; if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->firstLineIndent = d->doSnapping(d->firstLineIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } emit indentsChanged(false); break; case KoRulerPrivate::ParagraphIndent: if (d->rightToLeft) d->paragraphIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->paragraphIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->paragraphIndent = d->doSnapping(d->paragraphIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->paragraphIndent = activeLength - d->endIndent; emit indentsChanged(false); break; case KoRulerPrivate::EndIndent: if (d->rightToLeft) d->endIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); else d->endIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); if (!(ev->modifiers() & Qt::ShiftModifier)) { d->endIndent = d->doSnapping(d->endIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->endIndent = activeLength - d->paragraphIndent; emit indentsChanged(false); break; case KoRulerPrivate::Tab: d->tabMoved = true; if (d->currentIndex < 0) { // tab is deleted. if (ev->pos().y() < height()) { // reinstante it. d->currentIndex = d->tabs.count(); d->tabs.append(d->deletedTab); } else { break; } } if (d->rightToLeft) d->tabs[d->currentIndex].position = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->tabs[d->currentIndex].position = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if (!(ev->modifiers() & Qt::ShiftModifier)) d->tabs[d->currentIndex].position = d->doSnapping(d->tabs[d->currentIndex].position); if (d->tabs[d->currentIndex].position < 0) d->tabs[d->currentIndex].position = 0; if (d->tabs[d->currentIndex].position > activeLength) d->tabs[d->currentIndex].position = activeLength; if (ev->pos().y() > height() + OutsideRulerThreshold ) { // moved out of the ruler, delete it. d->deletedTab = d->tabs.takeAt(d->currentIndex); d->currentIndex = -1; // was that a temporary added tab? if ( d->originalIndex == -1 ) emit guideLineCreated(d->orientation, d->orientation == Qt::Horizontal ? d->viewConverter->viewToDocumentY(ev->pos().y()) : d->viewConverter->viewToDocumentX(ev->pos().x())); } d->emitTabChanged(); break; case KoRulerPrivate::HotSpot: qreal newPos; if (d->orientation == Qt::Horizontal) newPos= d->viewConverter->viewToDocumentX(pos.x() - d->offset); else newPos= d->viewConverter->viewToDocumentY(pos.y() - d->offset); d->hotspots[d->currentIndex].position = newPos; emit hotSpotChanged(d->hotspots[d->currentIndex].id, newPos); break; case KoRulerPrivate::None: d->mouseCoordinate = (d->orientation == Qt::Horizontal ? pos.x() : pos.y()) - d->offset; int hotSpotIndex = d->hotSpotIndex(pos); if (hotSpotIndex >= 0) { setCursor(QCursor( d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor )); break; } unsetCursor(); KoRulerPrivate::Selection selection = d->selectionAtPosition(pos); QString text; switch(selection) { case KoRulerPrivate::FirstLineIndent: text = i18n("First line indent"); break; case KoRulerPrivate::ParagraphIndent: text = i18n("Left indent"); break; case KoRulerPrivate::EndIndent: text = i18n("Right indent"); break; case KoRulerPrivate::None: if (ev->buttons() & Qt::LeftButton) { if (d->orientation == Qt::Horizontal && ev->pos().y() > height() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentY(ev->pos().y())); else if (d->orientation == Qt::Vertical && ev->pos().x() > width() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentX(ev->pos().x())); } break; default: break; } setToolTip(text); } update(); } void KoRuler::clearHotSpots() { if (d->hotspots.isEmpty()) return; d->hotspots.clear(); update(); } void KoRuler::setHotSpot(qreal position, int id) { uint hotspotCount = d->hotspots.count(); for( uint i = 0; i < hotspotCount; ++i ) { KoRulerPrivate::HotSpotData & hs = d->hotspots[i]; if (hs.id == id) { hs.position = position; update(); return; } } // not there yet, then insert it. KoRulerPrivate::HotSpotData hs; hs.position = position; hs.id = id; d->hotspots.append(hs); } bool KoRuler::removeHotSpot(int id) { QList::Iterator iter = d->hotspots.begin(); while(iter != d->hotspots.end()) { if (iter->id == id) { d->hotspots.erase(iter); update(); return true; } } return false; } void KoRuler::createGuideToolConnection(KoCanvasBase *canvas) { Q_ASSERT(canvas); KoToolBase *tool = KoToolManager::instance()->toolById(canvas, QLatin1String("GuidesTool")); if (!tool) return; // It's perfectly fine to have no guides tool, we don't have to warn the user about it connect(this, SIGNAL(guideLineCreated(Qt::Orientation,qreal)), tool, SLOT(createGuideLine(Qt::Orientation,qreal))); } + +void KoRuler::setUnitPixelMultiple2(bool enabled) +{ + if (enabled) { + d->pixelStep = 64.0; + } + else { + d->pixelStep = 100.0; + } +} diff --git a/libs/widgets/KoRuler.h b/libs/widgets/KoRuler.h index bc7a3f5a4a..a570ec6057 100644 --- a/libs/widgets/KoRuler.h +++ b/libs/widgets/KoRuler.h @@ -1,283 +1,285 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (C) 2006 Peter Simonsson Copyright (C) 2007 C. Boemann Copyright (C) 2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef koRuler_h #define koRuler_h #include "kritawidgets_export.h" #include #include class QPaintEvent; class KoViewConverter; class KoCanvasBase; class KoRulerPrivate; class KoUnit; /** * Decorator widget to draw a single ruler around a canvas. */ class KRITAWIDGETS_EXPORT KoRuler : public QWidget { Q_OBJECT public: /** * Creates a ruler with the orientation @p orientation * @param parent parent widget * @param orientation the orientation of the ruler * @param viewConverter the view converter used to convert from point to pixel */ KoRuler(QWidget* parent, Qt::Orientation orientation, const KoViewConverter* viewConverter); ~KoRuler() override; /// For paragraphs each tab definition is represented by this struct. struct Tab { qreal position; ///< distance in point from the start of the text-shape QTextOption::TabType type; ///< Determine which type is used. }; /// The ruler's unit KoUnit unit() const; /// The length of the ruler in points (pt) qreal rulerLength() const; /// The orientation of the ruler Qt::Orientation orientation() const; /// The start indent of the first line qreal firstLineIndent() const; /// The start indent of the rest of the lines qreal paragraphIndent() const; /// The end indent of all lines qreal endIndent() const; /// The tab chooser widget, which you must put into a layout along with the ruler. /// Returns 0 for vertical rulers, QWidget *tabChooser(); /** * set a list of actions that will be shown in a popup should the user right click on this ruler. * @param popupActionList the list of actions * @see popupActionList() */ void setPopupActionList(const QList &popupActionList); /** * Return the actions list. * @see setPopupActionList() */ QList popupActionList() const; /// reimplemented QSize minimumSizeHint() const override; /// reimplemented QSize sizeHint() const override; public Q_SLOTS: /// Set the unit of the ruler void setUnit(const KoUnit &unit); /** Set the offset. Use this function to sync the ruler with * the canvas' position on screen * @param offset The offset in pixels */ void setOffset(int offset); /// Sets the length of the ruler to @p length in points (pt) void setRulerLength(qreal length); /** Set the active range, ie the part of the ruler that is most likely used. * set to 0, 0 when there is no longer any active range * @param start the start of the range in pt * @param end the end of the range in pt */ void setActiveRange(qreal start, qreal end); /** Set the override active range, ie the part of the ruler that is most likely used. * set to 0, 0 when there is no longer any active range * The override, means that if set it takes precedence over the normal active range. * @param start the start of the range in pt * @param end the end of the range in pt */ void setOverrideActiveRange(qreal start, qreal end); /** Set the state of the ruler so that it shows everything in right to left mode. * @param isRightToLeft state of right to left mode. Default is false. */ void setRightToLeft(bool isRightToLeft); /** Set if the ruler should show indents as used in textditors. * Set the indents with setFirstLineIndent(), setParagraphIndent(), setEndIndent() . * @param show show indents if true. Default is false. */ void setShowIndents(bool show); /** Set the position of the first line start indent relative to the active range. * If Right To left is set the indent is relative to the right side of the active range . * @param indent the value relative to the active range. */ void setFirstLineIndent(qreal indent); /** Set the position of the rest of the lines start indent relative to the active range. * If Right To left is set the indent is relative to the right side of the active range . * @param indent the value relative to the active range. */ void setParagraphIndent(qreal indent); /** Set the position of the end indent relative to the active range. * If Right To left is set the indent is relative to the left side of the active range . * @param indent the value relative to the active range. */ void setEndIndent(qreal indent); /** Set whether the ruler should show the current mouse position. * Update the position with updateMouseCoordinate(). * @param show show mouse position if true. Default is false. */ void setShowMousePosition(bool show); /** * \see setShowMousePosition */ bool showMousePosition() const; /** Update the current position of the mouse pointer, repainting if changed. * The ruler offset will be applied before painting. * @param coordinate Either the x or y coordinate of the mouse depending * of the orientation of the ruler. */ void updateMouseCoordinate(int coordinate); /** * Set whether the ruler should show the selection borders * @param show show selection borders if true, default is false. */ void setShowSelectionBorders(bool show); /** * Update the selection borders * @param first the first selection border in points * @param second the other selection border in points */ void updateSelectionBorders(qreal first, qreal second); /** * Set whether the ruler should show tabs * @param show show selection borders if true, default is false. */ void setShowTabs(bool show); /** * Set whether the tabs is relative to the paragraph indent * @param relative tabs are relative to pragraph indent if true, default is false. */ void setRelativeTabs(bool relative); /** * Update the tabs * @param tabs a list of tabs that is shown on the ruler * @param tabDistance the distncte between regular interval tabs */ void updateTabs(const QList &tabs, qreal tabDistance); /*** * Return the list of tabs set on this ruler. */ QList tabs() const; /** * Clear all previously set hotspots. * A hotspot is a position on the ruler that the user can manipulate by dragging. */ void clearHotSpots(); /** * Add or set a hotspot. * A hotspot is a position on the ruler that the user can manipulate by dragging. * @param position the new position of the hotspot. * @param id the unique id for the hotspot. If the id has not been set before, it will be added. */ void setHotSpot(qreal position, int id = -1); /** * Remove a previously set hotspot, returning true if one is actually returned. * @param id the unique id for the hotspot. * A hotspot is a position on the ruler that the user can manipulate by dragging. */ bool removeHotSpot(int id); /** * Connect the ruler to a guides tool * This allows the user to drag a guide out of the ruler and get in one smooth operation * the guide tool to draw and position the guide line. * @param canvas the canvas that has had the KoToolManager create the tool for previously. */ void createGuideToolConnection(KoCanvasBase *canvas); + void setUnitPixelMultiple2(bool enabled); + Q_SIGNALS: /** * emitted when any of the indents is moved by the user. * @param final false until the user releases the mouse. So you can implement live update. */ void indentsChanged(bool final); /** * Emitted when any of the tabs are moved, deleted or inserted by the user. * @param originalTabIndex the index in the list of tabs before the user interaction * started, or -1 if this is a new tab * @param tab the new tab, or zero when the tab has been removed. */ void tabChanged(int originalTabIndex, KoRuler::Tab *tab); /// emitted when there the user is about to change a tab or hotspot void aboutToChange(); void hotSpotChanged(int id, qreal newPosition); /// emitted when the mouse is drag+released outside the ruler void guideLineCreated(Qt::Orientation orientation, qreal viewPosition); void guideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos); void guideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos); protected: /// reimplemented void paintEvent(QPaintEvent* event) override; /// reimplemented void mousePressEvent(QMouseEvent *ev) override; /// reimplemented void mouseReleaseEvent(QMouseEvent *ev) override; /// reimplemented void mouseMoveEvent(QMouseEvent *ev) override; private: KoRulerPrivate * const d; friend class KoRulerPrivate; }; #endif diff --git a/libs/widgets/KoRuler_p.h b/libs/widgets/KoRuler_p.h index da2f3f3db6..ce430de562 100644 --- a/libs/widgets/KoRuler_p.h +++ b/libs/widgets/KoRuler_p.h @@ -1,210 +1,212 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KORULER_P_H #define KORULER_P_H #include class RulerTabChooser : public QWidget { public: RulerTabChooser(QWidget *parent) : QWidget(parent), m_type(QTextOption::LeftTab), m_showTabs(false) {} ~RulerTabChooser() override {} inline QTextOption::TabType type() {return m_type;} void setShowTabs(bool showTabs) { if (m_showTabs == showTabs) return; m_showTabs = showTabs; update(); } void mousePressEvent(QMouseEvent *) override; void paintEvent(QPaintEvent *) override; private: QTextOption::TabType m_type; bool m_showTabs :1; }; class PaintingStrategy { public: /// constructor PaintingStrategy() {} /// destructor virtual ~PaintingStrategy() {} /** * Draw the background of the ruler. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** * Draw the indicators for text-tabs. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual void drawTabs(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** * Draw the indicators for the measurements which typically are drawn every [unit]. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. * @param rectangle */ virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle) = 0; /** * Draw the indicators for the indents of a text paragraph * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual void drawIndents(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** *returns the size suggestion for a ruler with this strategy. */ virtual QSize sizeHint() = 0; }; class HorizontalPaintingStrategy : public PaintingStrategy { public: HorizontalPaintingStrategy() : lengthInPixel(1) {} QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter) override; void drawTabs(const KoRulerPrivate *ruler, QPainter &painter) override; void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle) override; void drawIndents(const KoRulerPrivate *ruler, QPainter &painter) override; QSize sizeHint() override; private: qreal lengthInPixel; }; class VerticalPaintingStrategy : public PaintingStrategy { public: VerticalPaintingStrategy() : lengthInPixel(1) {} QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter) override; void drawTabs(const KoRulerPrivate *, QPainter &) override {} void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle) override; void drawIndents(const KoRulerPrivate *, QPainter &) override { } QSize sizeHint() override; private: qreal lengthInPixel; }; class HorizontalDistancesPaintingStrategy : public HorizontalPaintingStrategy { public: HorizontalDistancesPaintingStrategy() {} void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle) override; private: void drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end); }; class KoRulerPrivate { public: KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation orientation); ~KoRulerPrivate(); void emitTabChanged(); KoUnit unit; const Qt::Orientation orientation; const KoViewConverter * const viewConverter; int offset; qreal rulerLength; qreal activeRangeStart; qreal activeRangeEnd; qreal activeOverrideRangeStart; qreal activeOverrideRangeEnd; int mouseCoordinate; int showMousePosition; bool showSelectionBorders; qreal firstSelectionBorder; qreal secondSelectionBorder; bool showIndents; qreal firstLineIndent; qreal paragraphIndent; qreal endIndent; bool showTabs; bool relativeTabs; bool tabMoved; // set to true on first move of a selected tab QList tabs; int originalIndex; //index of selected tab before we started dragging it. int currentIndex; //index of selected tab or selected HotSpot - only valid when selected indicates tab or hotspot KoRuler::Tab deletedTab; qreal tabDistance; struct HotSpotData { qreal position; int id; }; QList hotspots; bool rightToLeft; enum Selection { None, Tab, FirstLineIndent, ParagraphIndent, EndIndent, HotSpot }; Selection selected; int selectOffset; QList popupActions; RulerTabChooser *tabChooser; // Cached painting strategies PaintingStrategy * normalPaintingStrategy; PaintingStrategy * distancesPaintingStrategy; // Current painting strategy PaintingStrategy * paintingStrategy; KoRuler *ruler; bool guideCreationStarted; + qreal pixelStep; + qreal numberStepForUnit() const; /// @return The rounding of value to the nearest multiple of stepValue qreal doSnapping(const qreal value) const; Selection selectionAtPosition(const QPoint & pos, int *selectOffset = 0); int hotSpotIndex(const QPoint & pos); qreal effectiveActiveRangeStart() const; qreal effectiveActiveRangeEnd() const; friend class VerticalPaintingStrategy; friend class HorizontalPaintingStrategy; }; #endif diff --git a/packaging/windows/package-complete.cmd b/packaging/windows/package-complete.cmd index ce1c778e69..5c667e7473 100644 --- a/packaging/windows/package-complete.cmd +++ b/packaging/windows/package-complete.cmd @@ -1,725 +1,757 @@ @echo off :: This batch script is meant to prepare a Krita package folder to be zipped or :: to be a base for the installer. :: -------- setlocal enabledelayedexpansion goto begin :: Subroutines :find_on_path out_variable file_name set %1=%~f$PATH:2 goto :EOF :get_dir_path out_variable file_path set %1=%~dp2 goto :EOF :get_full_path out_variable file_path setlocal set FULL_PATH=%~f2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) else ( if exist "%FULL_PATH%\" ( set FULL_PATH= ) ) endlocal & set "%1=%FULL_PATH%" goto :EOF :get_full_path_dir out_variable file_path setlocal set FULL_PATH=%~dp2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) endlocal & set "%1=%FULL_PATH%" goto :EOF :prompt_for_string out_variable prompt set /p %1=%~2^> goto :EOF :prompt_for_positive_integer out_variable prompt setlocal call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" set USER_INPUT=0 set /a RESULT=%USER_INPUT% if not %RESULT% GTR 0 ( set RESULT= ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_file out_variable prompt setlocal :prompt_for_file__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path RESULT "%USER_INPUT%" if "%RESULT%" == "" ( echo Input does not point to valid file! set USER_INPUT= goto prompt_for_file__retry ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_dir out_variable prompt setlocal :prompt_for_dir__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path_dir RESULT "%USER_INPUT%\" if "%RESULT%" == "" ( echo Input does not point to valid dir! set USER_INPUT= goto prompt_for_dir__retry ) endlocal & set "%1=%RESULT%" goto :EOF :usage echo Usage: echo %~n0 [--no-interactive --package-name ^] [ OPTIONS ... ] echo. echo Basic options: echo --no-interactive Run without interactive prompts echo When not specified, the script will prompt echo for some of the parameters. echo --package-name ^ Specify the package name echo. echo Path options: echo --src-dir ^ Specify Krita source dir echo If unspecified, this will be determined from echo the script location echo --deps-install-dir ^ Specify deps install dir echo --krita-install-dir ^ Specify Krita install dir echo. +echo Special options: +echo --pre-zip-hook ^ Specify a script to be called before +echo packaging the zip archive, can be used to +echo sign the binaries +echo. goto :EOF :usage_and_exit call :usage exit /b :usage_and_fail call :usage exit /b 100 :: ---------------------------- :begin echo Krita Windows packaging script echo. :: command-line args parsing set ARG_NO_INTERACTIVE= set ARG_PACKAGE_NAME= set ARG_SRC_DIR= set ARG_DEPS_INSTALL_DIR= set ARG_KRITA_INSTALL_DIR= +set ARG_PRE_ZIP_HOOK= :args_parsing_loop set CURRENT_MATCHED= if not "%1" == "" ( if "%1" == "--no-interactive" ( set ARG_NO_INTERACTIVE=1 set CURRENT_MATCHED=1 ) if "%1" == "--package-name" ( if not "%ARG_PACKAGE_NAME%" == "" ( echo ERROR: Arg --package-name specified more than once 1>&2 echo. goto usage_and_fail ) if "%~2" == "" ( echo ERROR: Arg --package-name is empty 1>&2 echo. goto usage_and_fail ) set "ARG_PACKAGE_NAME=%~2" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--src-dir" ( if not "%ARG_SRC_DIR%" == "" ( echo ERROR: Arg --src-dir specified more than once 1>&2 echo. goto usage_and_fail ) if not exist "%~f2\" ( echo ERROR: Arg --src-dir does not point to a directory 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_SRC_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-install-dir" ( if not "%ARG_DEPS_INSTALL_DIR%" == "" ( echo ERROR: Arg --deps-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-install-dir" ( if not "%ARG_KRITA_INSTALL_DIR%" == "" ( echo ERROR: Arg --krita-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) + if "%1" == "--pre-zip-hook" ( + if not "%ARG_PRE_ZIP_HOOK%" == "" ( + echo ERROR: Arg --pre-zip-hook specified more than once 1>&2 + echo. + goto usage_and_fail + ) + if "%~f2" == "" ( + echo ERROR: Arg --pre-zip-hook does not point to a valid path 1>&2 + echo. + goto usage_and_fail + ) + call :get_full_path ARG_PRE_ZIP_HOOK "%~f2" + shift /2 + set CURRENT_MATCHED=1 + ) if "%1" == "--help" ( goto usage_and_exit ) if not "!CURRENT_MATCHED!" == "1" ( echo ERROR: Unknown option %1 1>&2 echo. goto usage_and_fail ) shift /1 goto args_parsing_loop ) if "%ARG_NO_INTERACTIVE%" == "1" ( if "%ARG_PACKAGE_NAME%" == "" ( echo ERROR: Required arg --package-name not specified1>&2 echo. goto usage_and_fail ) ) if "%ARG_NO_INTERACTIVE%" == "1" ( echo Non-interactive mode ) else ( echo Interactive mode :: Trick to pause on exit call :real_begin pause exit /b !ERRORLEVEL! ) :real_begin echo. if "%ARG_PACKAGE_NAME%" == "" ( call :prompt_for_string ARG_PACKAGE_NAME "Provide package name" if "ARG_PACKAGE_NAME" == "" ( echo ERROR: Package name not set! 1>&2 exit /b 102 ) ) :: Check environment config if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7z.exe ) if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7za.exe ) if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe" if not exist "!SEVENZIP_EXE!" ( set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe" ) if not exist "!SEVENZIP_EXE!" ( echo 7-Zip not found! 1>&2 exit /b 102 ) ) echo 7-Zip: %SEVENZIP_EXE% if "%MINGW_BIN_DIR%" == "" ( call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" ) if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) else ( call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" echo Found mingw on PATH: !MINGW_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) ) ) ) echo mingw-w64: %MINGW_BIN_DIR% :: Windows SDK is needed for windeployqt to get d3dcompiler_xx.dll if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if exist "%WindowsSdkDir%\" ( pushd "%WindowsSdkDir%" if exist "bin\x64\fxc.exe" ( set HAVE_FXC_EXE=1 ) else ( for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( if exist "bin\%%a\x64\fxc.exe" ( set HAVE_FXC_EXE=1 ) ) ) popd ) if not "%HAVE_FXC_EXE%" == "1" ( set WindowsSdkDir= echo Windows SDK 10 with fxc.exe not found echo If Qt was built with ANGLE ^(dynamic OpenGL^) support, the package might not work properly on some systems! ) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% if not "%ARG_SRC_DIR%" == "" ( set "KRITA_SRC_DIR=%ARG_SRC_DIR%" ) if "%KRITA_SRC_DIR%" == "" ( :: Check whether this looks like to be in the source tree set "_temp=%~dp0" if "!_temp:~-19!" == "\packaging\windows\" ( if exist "!_temp:~0,-19!\CMakeLists.txt" ( if exist "!_temp:~0,-19!\3rdparty\CMakeLists.txt" ( set "KRITA_SRC_DIR=!_temp:~0,-19!\" echo Script is running inside Krita src dir ) ) ) ) if "%KRITA_SRC_DIR%" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir" ) if "!KRITA_SRC_DIR!" == "" ( echo ERROR: Krita src dir not found! 1>&2 exit /b 102 ) ) echo Krita src: %KRITA_SRC_DIR% if not "%ARG_DEPS_INSTALL_DIR%" == "" ( set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%" ) if "%DEPS_INSTALL_DIR%" == "" ( set DEPS_INSTALL_DIR=%CD%\i_deps\ echo Using default deps install dir: !DEPS_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir" ) ) if "!DEPS_INSTALL_DIR!" == "" ( echo ERROR: Deps install dir not set! 1>&2 exit /b 102 ) ) echo Deps install dir: %DEPS_INSTALL_DIR% if not "%ARG_KRITA_INSTALL_DIR%" == "" ( set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%" ) if "%KRITA_INSTALL_DIR%" == "" ( set KRITA_INSTALL_DIR=%CD%\i\ echo Using default Krita install dir: !KRITA_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir" ) ) if "!KRITA_INSTALL_DIR!" == "" ( echo ERROR: Krita install dir not set! 1>&2 exit /b 102 ) ) echo Krita install dir: %KRITA_INSTALL_DIR% :: Simple checking if not exist "%DEPS_INSTALL_DIR%\" ( echo ERROR: Cannot find the deps install folder! 1>&2 exit /B 1 ) if not exist "%KRITA_INSTALL_DIR%\" ( echo ERROR: Cannot find the krita install folder! 1>&2 exit /B 1 ) if not "%DEPS_INSTALL_DIR: =%" == "%DEPS_INSTALL_DIR%" ( echo ERROR: Deps install path contains space, which will not work properly! 1>&2 exit /B 1 ) if not "%KRITA_INSTALL_DIR: =%" == "%KRITA_INSTALL_DIR%" ( echo ERROR: Krita install path contains space, which will not work properly! 1>&2 exit /B 1 ) set pkg_name=%ARG_PACKAGE_NAME% echo Package name is "%pkg_name%" set pkg_root=%CD%\%pkg_name% echo Packaging dir is %pkg_root% echo. if exist %pkg_root% ( echo ERROR: Packaging dir already exists! Please remove or rename it first. exit /B 1 ) if exist %pkg_root%.zip ( echo ERROR: Packaging zip already exists! Please remove or rename it first. exit /B 1 ) if exist %pkg_root%-dbg.zip ( echo ERROR: Packaging debug zip already exists! Please remove or rename it first. exit /B 1 ) echo. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is the above ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 1 ) echo. ) :: Initialize clean PATH set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\ set PATH=%MINGW_BIN_DIR%;%DEPS_INSTALL_DIR%\bin;%PATH% echo. echo Trying to guess GCC version... g++ --version > NUL if errorlevel 1 ( echo ERROR: g++ is not working. exit /B 1 ) for /f "delims=" %%a in ('g++ --version ^| find "g++"') do set GCC_VERSION_LINE=%%a echo -- %GCC_VERSION_LINE% if "%GCC_VERSION_LINE:tdm64=%" == "%GCC_VERSION_LINE%" ( echo Compiler doesn't look like TDM64-GCC, assuming simple mingw-w64 set IS_TDM= ) else ( echo Compiler looks like TDM64-GCC set IS_TDM=1 ) echo. echo Trying to guess target architecture... objdump --version > NUL if errorlevel 1 ( echo ERROR: objdump is not working. exit /B 1 ) for /f "delims=, tokens=1" %%a in ('objdump -f %KRITA_INSTALL_DIR%\bin\krita.exe ^| find "architecture"') do set TARGET_ARCH_LINE=%%a echo -- %TARGET_ARCH_LINE% if "%TARGET_ARCH_LINE:x86-64=%" == "%TARGET_ARCH_LINE%" ( echo Target looks like x86 set IS_X64= ) else ( echo Target looks like x64 set IS_x64=1 ) echo. echo Testing for objcopy... objcopy --version > NUL if errorlevel 1 ( echo ERROR: objcopy is not working. exit /B 1 ) echo. echo Testing for strip... strip --version > NUL if errorlevel 1 ( echo ERROR: strip is not working. exit /B 1 ) echo. echo Creating base directories... mkdir %pkg_root% && ^ mkdir %pkg_root%\bin && ^ mkdir %pkg_root%\lib && ^ mkdir %pkg_root%\share if errorlevel 1 ( echo ERROR: Cannot create packaging dir tree! %PAUSE% exit /B 1 ) echo. echo Copying GCC libraries... if x%IS_TDM% == x ( if x%is_x64% == x ( :: mingw-w64 x86 set "STDLIBS=gcc_s_dw2-1 gomp-1 stdc++-6 winpthread-1" ) else ( :: mingw-w64 x64 set "STDLIBS=gcc_s_seh-1 gomp-1 stdc++-6 winpthread-1" ) ) else ( if x%is_x64% == x ( :: TDM-GCC x86 set "STDLIBS=gomp-1" ) else ( :: TDM-GCC x64 set "STDLIBS=gomp_64-1" ) ) for %%L in (%STDLIBS%) do copy "%MINGW_BIN_DIR%\lib%%L.dll" %pkg_root%\bin echo. echo Copying files... :: krita.exe copy %KRITA_INSTALL_DIR%\bin\krita.exe %pkg_root%\bin :: DLLs from bin/ echo INFO: Copying all DLLs except Qt5* from bin/ setlocal enableextensions enabledelayedexpansion for /f "delims=" %%F in ('dir /b "%KRITA_INSTALL_DIR%\bin\*.dll"') do ( set file=%%F set file=!file:~0,3! if not x!file! == xQt5 copy %KRITA_INSTALL_DIR%\bin\%%F %pkg_root%\bin ) for /f "delims=" %%F in ('dir /b "%DEPS_INSTALL_DIR%\bin\*.dll"') do ( set file=%%F set file=!file:~0,3! if not x!file! == xQt5 copy %DEPS_INSTALL_DIR%\bin\%%F %pkg_root%\bin ) endlocal :: symsrv.yes for Dr. Mingw copy %DEPS_INSTALL_DIR%\bin\symsrv.yes %pkg_root%\bin :: DLLs from lib/ echo INFO: Copying all DLLs from lib/ (deps) copy %DEPS_INSTALL_DIR%\lib\*.dll %pkg_root%\bin :: Boost, there might be more than one leftover but we can't really do much copy %DEPS_INSTALL_DIR%\bin\libboost_system-*.dll %pkg_root%\bin :: KF5 plugins may be placed at different locations depending on how Qt is built xcopy /S /Y /I %DEPS_INSTALL_DIR%\lib\plugins\imageformats %pkg_root%\bin\imageformats xcopy /S /Y /I %DEPS_INSTALL_DIR%\plugins\imageformats %pkg_root%\bin\imageformats xcopy /S /Y /I %DEPS_INSTALL_DIR%\lib\plugins\kf5 %pkg_root%\bin\kf5 xcopy /S /Y /I %DEPS_INSTALL_DIR%\plugins\kf5 %pkg_root%\bin\kf5 :: Qt Translations :: it seems that windeployqt does these, but only *some* of these??? mkdir %pkg_root%\bin\translations setlocal enableextensions enabledelayedexpansion for /f "delims=" %%F in ('dir /b "%DEPS_INSTALL_DIR%\translations\qt_*.qm"') do ( :: Exclude qt_help_*.qm set temp=%%F set temp2=!temp:_help=! if x!temp2! == x!temp! copy %DEPS_INSTALL_DIR%\translations\!temp! %pkg_root%\bin\translations\!temp! ) endlocal :: Krita plugins xcopy /Y %KRITA_INSTALL_DIR%\lib\kritaplugins\*.dll %pkg_root%\lib\kritaplugins\ :: Share xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\color %pkg_root%\share\color xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\color-schemes %pkg_root%\share\color-schemes xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\icons %pkg_root%\share\icons xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\krita %pkg_root%\share\krita xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\kritaplugins %pkg_root%\share\kritaplugins xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\kf5 %pkg_root%\share\kf5 xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\mime %pkg_root%\share\mime :: Python libs xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\krita\pykrita %pkg_root%\share\krita\pykrita :: Not useful on Windows it seems rem xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\appdata %pkg_root%\share\appdata rem xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\applications %pkg_root%\share\applications rem xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\doc %pkg_root%\share\doc rem xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\kservices5 %pkg_root%\share\kservices5 rem xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\man %pkg_root%\share\man rem xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\ocio %pkg_root%\share\ocio :: Copy locale to bin xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\locale %pkg_root%\bin\locale xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\locale %pkg_root%\bin\locale :: Copy shortcut link from source (can't create it dynamically) copy %KRITA_SRC_DIR%\packaging\windows\krita.lnk %pkg_root% set "QMLDIR_ARGS=--qmldir %DEPS_INSTALL_DIR%\qml" if exist "%KRITA_INSTALL_DIR%\lib\qml" ( xcopy /Y /S /I %KRITA_INSTALL_DIR%\lib\qml %pkg_root%\bin :: This doesn't really seem to do anything set "QMLDIR_ARGS=%QMLDIR_ARGS% --qmldir %KRITA_INSTALL_DIR%\lib\qml" ) if EXIST "%DEPS_INSTALL_DIR%\bin\gmic_krita_qt.exe" ( copy %DEPS_INSTALL_DIR%\bin\gmic_krita_qt.exe %pkg_root%\bin set "WINDEPLOYQT_GMIC_ARGS=%pkg_root%\bin\gmic_krita_qt.exe" ) :: windeployqt windeployqt.exe %QMLDIR_ARGS% --release -gui -core -concurrent -network -printsupport -svg -xml -multimedia -qml -quick -quickwidgets %pkg_root%\bin\krita.exe %WINDEPLOYQT_GMIC_ARGS% if errorlevel 1 ( echo ERROR: WinDeployQt failed! 1>&2 exit /B 1 ) :: Copy embedded Python xcopy /Y /S /I %DEPS_INSTALL_DIR%\python %pkg_root%\python :: For chopping relative path :: 512 should be enough :: n+2 to also account for a trailing backslash setlocal enableextensions enabledelayedexpansion for /L %%n in (1 1 512) do if "!pkg_root:~%%n,1!" neq "" set /a "pkg_root_len_plus_one=%%n+2" endlocal & set pkg_root_len_plus_one=%pkg_root_len_plus_one% echo. setlocal enableextensions enabledelayedexpansion :: Remove Python cache files for /d /r "%pkg_root%" %%F in (__pycache__\) do ( if EXIST "%%F" ( set relpath=%%F set relpath=!relpath:~%pkg_root_len_plus_one%! echo Deleting Python cache !relpath! rmdir /S /Q "%%F" ) ) endlocal echo. echo Splitting debug info from binaries... call :split-debug "%pkg_root%\bin\krita.exe" bin\krita.exe setlocal enableextensions enabledelayedexpansion :: Find all DLLs for /r "%pkg_root%" %%F in (*.dll) do ( set relpath=%%F set relpath=!relpath:~%pkg_root_len_plus_one%! call :split-debug "%%F" !relpath! ) endlocal setlocal enableextensions enabledelayedexpansion :: Find all Python native modules for /r "%pkg_root%\share\krita\pykrita\" %%F in (*.pyd) do ( set relpath=%%F set relpath=!relpath:~%pkg_root_len_plus_one%! call :split-debug "%%F" !relpath! ) endlocal +if not "%ARG_PRE_ZIP_HOOK%" == "" ( + echo Running pre-zip-hook... + setlocal + cmd /c "%ARG_PRE_ZIP_HOOK%" "%pkg_root%\" + if errorlevel 1 ( + echo ERROR: Got exit code !errorlevel! from pre-zip-hook! 1>&2 + exit /b 1 + ) + endlocal +) + echo. echo Packaging stripped binaries... "%SEVENZIP_EXE%" a -tzip %pkg_name%.zip %pkg_root%\ "-xr^!.debug" echo -------- echo. echo Packaging debug info... :: (note that the top-level package dir is not included) "%SEVENZIP_EXE%" a -tzip %pkg_name%-dbg.zip -r %pkg_root%\*.debug echo -------- echo. echo. echo Krita packaged as %pkg_name%.zip if exist %pkg_name%-dbg.zip echo Debug info packaged as %pkg_name%-dbg.zip echo Packaging dir is %pkg_root% echo NOTE: Do not create installer with packaging dir. Extract from echo %pkg_name%.zip instead, echo and do _not_ run krita inside the extracted directory because it will echo create extra unnecessary files. echo. echo Please remember to actually test the package before releasing it. echo. %PAUSE% exit /b :split-debug echo Splitting debug info of %2 objcopy --only-keep-debug %~1 %~1.debug if ERRORLEVEL 1 exit /b %ERRORLEVEL% :: If the debug file is small enough then consider there being no debug info. :: Discard these files since they somehow make gdb crash. call :getfilesize %~1.debug if /i %getfilesize_retval% LEQ 2048 ( echo Discarding %2.debug del %~1.debug exit /b 0 ) if not exist %~dp1.debug mkdir %~dp1.debug move %~1.debug %~dp1.debug\ > NUL strip %~1 :: Add debuglink :: FIXME: There is a problem with gdb that cause it to output this warning :: FIXME: "warning: section .gnu_debuglink not found in xxx.debug" :: FIXME: I tried adding a link to itself but this kills drmingw :( objcopy --add-gnu-debuglink="%~dp1.debug\%~nx1.debug" %~1 exit /b %ERRORLEVEL% :getfilesize set getfilesize_retval=%~z1 goto :eof :relpath_dirpath call :relpath_dirpath_internal "" "%~1" goto :eof :relpath_dirpath_internal for /f "tokens=1* delims=\" %%a in ("%~2") do ( :: If part 2 is empty, it means part 1 is probably the file name if x%%b==x ( set relpath_dirpath_retval=%~1 ) else ( call :relpath_dirpath_internal "%~1%%a\" %%b ) ) goto :eof diff --git a/packaging/windows/sign-package.cmd b/packaging/windows/sign-package.cmd new file mode 100644 index 0000000000..2b3222f941 --- /dev/null +++ b/packaging/windows/sign-package.cmd @@ -0,0 +1,56 @@ +@echo off +setlocal enableextensions enabledelayedexpansion + +set pkg_root=%~f1 + +if not "%SIGNTOOL%" == "" goto skip_find_signtool + +:: Find Windows SDK for signtool.exe +if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" +if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" +if exist "%WindowsSdkDir%\" ( + pushd "%WindowsSdkDir%" + for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( + if exist "bin\%%a\x64\signtool.exe" ( + set "SIGNTOOL=%WindowsSdkDir%\bin\%%a\x64\signtool.exe" + ) + ) + if "%SIGNTOOL%" == "" if exist "bin\x64\signtool.exe" ( + set "SIGNTOOL=%WindowsSdkDir%\bin\x64\signtool.exe" + ) + popd +) +if "%SIGNTOOL%" == "" ( + echo ERROR: signtool not found 1>&2 + exit /b 1 +) + +:skip_find_signtool + +if "%SIGNTOOL_SIGN_FLAGS%" == "" ( + echo ERROR: Please set environment variable SIGNTOOL_SIGN_FLAGS 1>&2 + exit /b 1 + :: This is what I used for testing: + :: set "SIGNTOOL_SIGN_FLAGS=/f "C:\Users\Alvin\MySPC.pfx" /t http://timestamp.verisign.com/scripts/timstamp.dll" +) + +echo Signing binaries in "%pkg_root%" +if not exist "%pkg_root%\" ( + echo ERROR: No packaging dir %pkg_root% 1>&2 + exit /b 1 +) +for /r "%pkg_root%\" %%F in (*.exe *.dll *.pyd) do ( + :: Check for existing signature + "%SIGNTOOL%" verify /q /pa "%%F" > NUL + if errorlevel 1 ( + echo Signing %%F + "%SIGNTOOL%" sign %SIGNTOOL_SIGN_FLAGS% "%%F" + if errorlevel 1 ( + echo ERROR: Got exit code !errorlevel! from signtool! 1>&2 + exit /b 1 + ) + ) else ( + echo Not signing %%F - file already signed + ) +) +endlocal diff --git a/plugins/assistants/Assistants/AssistantsToolOptions.ui b/plugins/assistants/Assistants/AssistantsToolOptions.ui new file mode 100644 index 0000000000..32c546698a --- /dev/null +++ b/plugins/assistants/Assistants/AssistantsToolOptions.ui @@ -0,0 +1,147 @@ + + + AssistantsToolOptions + + + + 0 + 0 + 188 + 211 + + + + + + + + + + 0 + 0 + + + + Add: + + + + + + + + + + + + + + Open... + + + + + + + Save... + + + + + + + + + Delete all + + + + + + + + + + 0 + 0 + + + + Opacity: + + + + + + + 100 + + + + + + + + + + + + 0 + 0 + + + + Color: + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + KColorButton + QPushButton +

kcolorbutton.h
+ 1 + + + KisSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
+ + + + diff --git a/plugins/assistants/Assistants/CMakeLists.txt b/plugins/assistants/Assistants/CMakeLists.txt new file mode 100644 index 0000000000..63bcecbb0f --- /dev/null +++ b/plugins/assistants/Assistants/CMakeLists.txt @@ -0,0 +1,25 @@ +set(kritaassistanttool_SOURCES + assistant_tool.cc + ConcentricEllipseAssistant.cc + Ellipse.cc + EllipseAssistant.cc + FisheyePointAssistant.cc + InfiniteRulerAssistant.cc + kis_assistant_tool.cc + ParallelRulerAssistant.cc + PerspectiveAssistant.cc + Ruler.cc + RulerAssistant.cc + SplineAssistant.cc + VanishingPointAssistant.cc +) + +ki18n_wrap_ui(kritaassistanttool_SOURCES AssistantsToolOptions.ui ) + +add_library(kritaassistanttool MODULE ${kritaassistanttool_SOURCES}) + +target_link_libraries(kritaassistanttool kritaui kritaflake ) + +install(TARGETS kritaassistanttool DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) + +install( FILES krita_tool_assistant.png dark_krita_tool_assistant.png light_krita_tool_assistant.png DESTINATION ${DATA_INSTALL_DIR}/krita/pics) diff --git a/plugins/assistants/RulerAssistant/ConcentricEllipseAssistant.cc b/plugins/assistants/Assistants/ConcentricEllipseAssistant.cc similarity index 89% rename from plugins/assistants/RulerAssistant/ConcentricEllipseAssistant.cc rename to plugins/assistants/Assistants/ConcentricEllipseAssistant.cc index d0b508aa3e..e50d8e4484 100644 --- a/plugins/assistants/RulerAssistant/ConcentricEllipseAssistant.cc +++ b/plugins/assistants/Assistants/ConcentricEllipseAssistant.cc @@ -1,181 +1,195 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ConcentricEllipseAssistant.h" #include #include "kis_debug.h" #include #include #include #include #include #include #include ConcentricEllipseAssistant::ConcentricEllipseAssistant() : KisPaintingAssistant("concentric ellipse", i18n("Concentric Ellipse assistant")) { } QPointF ConcentricEllipseAssistant::project(const QPointF& pt, const QPointF& strokeBegin) const { - Q_ASSERT(handles().size() == 3); + Q_ASSERT(isAssistantComplete()); e.set(*handles()[0], *handles()[1], *handles()[2]); qreal dx = pt.x() - strokeBegin.x(), dy = pt.y() - strokeBegin.y(); if (dx * dx + dy * dy < 4.0) { // allow some movement before snapping return strokeBegin; } //calculate ratio QPointF initial = e.project(strokeBegin); QPointF center = e.boundingRect().center(); qreal Ratio = QLineF(center, strokeBegin).length() /QLineF(center, initial).length(); //calculate the points of the extrapolated ellipse. QLineF extrapolate0 = QLineF(center, *handles()[0]); extrapolate0.setLength(extrapolate0.length()*Ratio); QLineF extrapolate1 = QLineF(center, *handles()[1]); extrapolate1.setLength(extrapolate1.length()*Ratio); QLineF extrapolate2 = QLineF(center, *handles()[2]); extrapolate2.setLength(extrapolate2.length()*Ratio); //set the extrapolation ellipse. extraE.set(extrapolate0.p2(), extrapolate1.p2(), extrapolate2.p2()); return extraE.project(pt); } QPointF ConcentricEllipseAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin) { return project(pt, strokeBegin); } void ConcentricEllipseAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { gc.save(); gc.resetTransform(); QPointF mousePos; if (canvas){ //simplest, cheapest way to get the mouse-position// mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist in the ellipse assistant, you may have passed arguments incorrectly:"<documentToWidgetTransform(); - if (outline()==true && previewVisible==true){ - if (handles().size() > 2){ + if (isSnappingActive() && previewVisible == true){ + + if (isAssistantComplete()){ if (e.set(*handles()[0], *handles()[1], *handles()[2])) { QPointF initial = e.project(initialTransform.inverted().map(mousePos)); QPointF center = e.boundingRect().center(); qreal Ratio = QLineF(center, initialTransform.inverted().map(mousePos)).length() /QLineF(center, initial).length(); //line from center to handle 1 * difference. //set handle1 translated to // valid ellipse gc.setTransform(initialTransform); gc.setTransform(e.getInverse(), true); QPainterPath path; // Draw the ellipse path.addEllipse(QPointF(0, 0), e.semiMajor()*Ratio, e.semiMinor()*Ratio); drawPreview(gc, path); } } } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); } void ConcentricEllipseAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { + if (assistantVisible == false || handles().size() < 2) { // 2 points means a line, so we can continue after 1 point + return; + } + + QTransform initialTransform = converter->documentToWidgetTransform(); - if (assistantVisible==false){return;} - if (handles().size() < 2) return; - QTransform initialTransform = converter->documentToWidgetTransform(); if (handles().size() == 2) { // just draw the axis gc.setTransform(initialTransform); QPainterPath path; path.moveTo(*handles()[0]); path.lineTo(*handles()[1]); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); return; } + if (e.set(*handles()[0], *handles()[1], *handles()[2])) { // valid ellipse gc.setTransform(initialTransform); gc.setTransform(e.getInverse(), true); QPainterPath path; path.moveTo(QPointF(-e.semiMajor(), 0)); path.lineTo(QPointF(e.semiMajor(), 0)); path.moveTo(QPointF(0, -e.semiMinor())); path.lineTo(QPointF(0, e.semiMinor())); // Draw the ellipse path.addEllipse(QPointF(0, 0), e.semiMajor(), e.semiMinor()); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); } } QRect ConcentricEllipseAssistant::boundingRect() const { - if (handles().size() != 3) return KisPaintingAssistant::boundingRect(); + if (!isAssistantComplete()) { + return KisPaintingAssistant::boundingRect(); + } + if (e.set(*handles()[0], *handles()[1], *handles()[2])) { return e.boundingRect().adjusted(-2, -2, 2, 2).toAlignedRect(); } else { return QRect(); } } QPointF ConcentricEllipseAssistant::buttonPosition() const { return (*handles()[0] + *handles()[1]) * 0.5; } +bool ConcentricEllipseAssistant::isAssistantComplete() const +{ + return handles().size() >= 3; +} + + ConcentricEllipseAssistantFactory::ConcentricEllipseAssistantFactory() { } ConcentricEllipseAssistantFactory::~ConcentricEllipseAssistantFactory() { } QString ConcentricEllipseAssistantFactory::id() const { return "concentric ellipse"; } QString ConcentricEllipseAssistantFactory::name() const { return i18n("Concentric Ellipse"); } KisPaintingAssistant* ConcentricEllipseAssistantFactory::createPaintingAssistant() const { return new ConcentricEllipseAssistant; } diff --git a/plugins/assistants/RulerAssistant/ConcentricEllipseAssistant.h b/plugins/assistants/Assistants/ConcentricEllipseAssistant.h similarity index 95% rename from plugins/assistants/RulerAssistant/ConcentricEllipseAssistant.h rename to plugins/assistants/Assistants/ConcentricEllipseAssistant.h index 79aa2435e1..2512d0d252 100644 --- a/plugins/assistants/RulerAssistant/ConcentricEllipseAssistant.h +++ b/plugins/assistants/Assistants/ConcentricEllipseAssistant.h @@ -1,54 +1,57 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _CONCENTRIC_ELLIPSE_ASSISTANT_H_ #define _CONCENTRIC_ELLIPSE_ASSISTANT_H_ #include "kis_painting_assistant.h" #include "Ellipse.h" #include #include class ConcentricEllipseAssistant : public KisPaintingAssistant { public: ConcentricEllipseAssistant(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; QPointF buttonPosition() const override; int numHandles() const override { return 3; } + bool isAssistantComplete() const; + protected: QRect boundingRect() const override; void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible=true, bool previewVisible=true) override; void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt, const QPointF& strokeBegin) const; mutable Ellipse e; mutable Ellipse extraE; }; class ConcentricEllipseAssistantFactory : public KisPaintingAssistantFactory { public: ConcentricEllipseAssistantFactory(); ~ConcentricEllipseAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/RulerAssistant/Ellipse.cc b/plugins/assistants/Assistants/Ellipse.cc similarity index 100% rename from plugins/assistants/RulerAssistant/Ellipse.cc rename to plugins/assistants/Assistants/Ellipse.cc diff --git a/plugins/assistants/RulerAssistant/Ellipse.h b/plugins/assistants/Assistants/Ellipse.h similarity index 100% rename from plugins/assistants/RulerAssistant/Ellipse.h rename to plugins/assistants/Assistants/Ellipse.h diff --git a/plugins/assistants/RulerAssistant/EllipseAssistant.cc b/plugins/assistants/Assistants/EllipseAssistant.cc similarity index 86% rename from plugins/assistants/RulerAssistant/EllipseAssistant.cc rename to plugins/assistants/Assistants/EllipseAssistant.cc index 3a65856e06..71bf0bf1b0 100644 --- a/plugins/assistants/RulerAssistant/EllipseAssistant.cc +++ b/plugins/assistants/Assistants/EllipseAssistant.cc @@ -1,154 +1,169 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "EllipseAssistant.h" #include #include "kis_debug.h" #include #include #include #include #include #include EllipseAssistant::EllipseAssistant() : KisPaintingAssistant("ellipse", i18n("Ellipse assistant")) { } QPointF EllipseAssistant::project(const QPointF& pt) const { - Q_ASSERT(handles().size() == 3); + Q_ASSERT(isAssistantComplete()); e.set(*handles()[0], *handles()[1], *handles()[2]); return e.project(pt); } QPointF EllipseAssistant::adjustPosition(const QPointF& pt, const QPointF& /*strokeBegin*/) { return project(pt); } void EllipseAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { gc.save(); gc.resetTransform(); QPoint mousePos; if (canvas){ //simplest, cheapest way to get the mouse-position// mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist in the ellipse assistant, you may have passed arguments incorrectly:"<documentToWidgetTransform(); - if (outline()==true && boundingRect().contains(initialTransform.inverted().map(mousePos), false) && previewVisible==true){ - if (handles().size() > 2){ + if (isSnappingActive() && boundingRect().contains(initialTransform.inverted().map(mousePos), false) && previewVisible==true){ + + if (isAssistantComplete()){ if (e.set(*handles()[0], *handles()[1], *handles()[2])) { // valid ellipse gc.setTransform(initialTransform); gc.setTransform(e.getInverse(), true); QPainterPath path; //path.moveTo(QPointF(-e.semiMajor(), 0)); path.lineTo(QPointF(e.semiMajor(), 0)); //path.moveTo(QPointF(0, -e.semiMinor())); path.lineTo(QPointF(0, e.semiMinor())); // Draw the ellipse path.addEllipse(QPointF(0, 0), e.semiMajor(), e.semiMinor()); drawPreview(gc, path); } } } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); } void EllipseAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { - if (assistantVisible==false){return;} - if (handles().size() < 2) return; - QTransform initialTransform = converter->documentToWidgetTransform(); + if (assistantVisible == false || handles().size() < 2){ + return; + } + + QTransform initialTransform = converter->documentToWidgetTransform(); + if (handles().size() == 2) { // just draw the axis gc.setTransform(initialTransform); QPainterPath path; path.moveTo(*handles()[0]); path.lineTo(*handles()[1]); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); return; } if (e.set(*handles()[0], *handles()[1], *handles()[2])) { // valid ellipse gc.setTransform(initialTransform); gc.setTransform(e.getInverse(), true); QPainterPath path; path.moveTo(QPointF(-e.semiMajor(), 0)); path.lineTo(QPointF(e.semiMajor(), 0)); path.moveTo(QPointF(0, -e.semiMinor())); path.lineTo(QPointF(0, e.semiMinor())); // Draw the ellipse path.addEllipse(QPointF(0, 0), e.semiMajor(), e.semiMinor()); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); } } QRect EllipseAssistant::boundingRect() const { - if (handles().size() != 3) return KisPaintingAssistant::boundingRect(); + if (!isAssistantComplete()) { + return KisPaintingAssistant::boundingRect(); + } + if (e.set(*handles()[0], *handles()[1], *handles()[2])) { return e.boundingRect().adjusted(-2, -2, 2, 2).toAlignedRect(); } else { return QRect(); } } QPointF EllipseAssistant::buttonPosition() const { return (*handles()[0] + *handles()[1]) * 0.5; } +bool EllipseAssistant::isAssistantComplete() const +{ + return handles().size() >= 3; +} + + + EllipseAssistantFactory::EllipseAssistantFactory() { } EllipseAssistantFactory::~EllipseAssistantFactory() { } QString EllipseAssistantFactory::id() const { return "ellipse"; } QString EllipseAssistantFactory::name() const { return i18n("Ellipse"); } KisPaintingAssistant* EllipseAssistantFactory::createPaintingAssistant() const { return new EllipseAssistant; } diff --git a/plugins/assistants/RulerAssistant/EllipseAssistant.h b/plugins/assistants/Assistants/EllipseAssistant.h similarity index 95% rename from plugins/assistants/RulerAssistant/EllipseAssistant.h rename to plugins/assistants/Assistants/EllipseAssistant.h index 8538cd77a3..6824b4e873 100644 --- a/plugins/assistants/RulerAssistant/EllipseAssistant.h +++ b/plugins/assistants/Assistants/EllipseAssistant.h @@ -1,52 +1,55 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ELLIPSE_ASSISTANT_H_ #define _ELLIPSE_ASSISTANT_H_ #include "kis_painting_assistant.h" #include "Ellipse.h" #include class EllipseAssistant : public KisPaintingAssistant { public: EllipseAssistant(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; QPointF buttonPosition() const override; int numHandles() const override { return 3; } + bool isAssistantComplete() const; + protected: QRect boundingRect() const override; void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible=true, bool previewVisible=true) override; void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt) const; mutable Ellipse e; }; class EllipseAssistantFactory : public KisPaintingAssistantFactory { public: EllipseAssistantFactory(); ~EllipseAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/RulerAssistant/FisheyePointAssistant.cc b/plugins/assistants/Assistants/FisheyePointAssistant.cc similarity index 93% rename from plugins/assistants/RulerAssistant/FisheyePointAssistant.cc rename to plugins/assistants/Assistants/FisheyePointAssistant.cc index aa8eba2b4c..b610d4afb9 100644 --- a/plugins/assistants/RulerAssistant/FisheyePointAssistant.cc +++ b/plugins/assistants/Assistants/FisheyePointAssistant.cc @@ -1,217 +1,227 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2014 Wolthera van Hövell tot Westerflier + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "FisheyePointAssistant.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include FisheyePointAssistant::FisheyePointAssistant() : KisPaintingAssistant("fisheye-point", i18n("Fish Eye Point assistant")) { } QPointF FisheyePointAssistant::project(const QPointF& pt, const QPointF& strokeBegin) { const static QPointF nullPoint(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); - Q_ASSERT(handles().size() == 3); + Q_ASSERT(isAssistantComplete()); e.set(*handles()[0], *handles()[1], *handles()[2]); qreal dx = pt.x() - strokeBegin.x(), dy = pt.y() - strokeBegin.y(); if (dx * dx + dy * dy < 4.0) { // allow some movement before snapping return strokeBegin; } //set the extrapolation ellipse. if (e.set(*handles()[0], *handles()[1], *handles()[2])){ QLineF radius(*handles()[1], *handles()[0]); radius.setAngle(fmod(radius.angle()+180.0,360.0)); QLineF radius2(*handles()[0], *handles()[1]); radius2.setAngle(fmod(radius2.angle()+180.0,360.0)); if ( extraE.set(*handles()[0], *handles()[1],strokeBegin ) ) { return extraE.project(pt); } else if (extraE.set(radius.p1(), radius.p2(),strokeBegin)) { return extraE.project(pt); } else if (extraE.set(radius2.p1(), radius2.p2(),strokeBegin)){ return extraE.project(pt); } } return nullPoint; } QPointF FisheyePointAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin) { return project(pt, strokeBegin); } void FisheyePointAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { gc.save(); gc.resetTransform(); QPointF delta(0,0); QPointF mousePos(0,0); QPointF endPoint(0,0);//this is the final point that the line is being extended to, we seek it just outside the view port// QPointF otherHandle(0,0); if (canvas){ //simplest, cheapest way to get the mouse-position// mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<documentToWidgetTransform(); - if (outline()==true && previewVisible==true){ - if (handles().size() > 2){ - - + if (isSnappingActive() && previewVisible == true ) { + + if (isAssistantComplete()){ + if (e.set(*handles()[0], *handles()[1], *handles()[2])) { if (extraE.set(*handles()[0], *handles()[1], initialTransform.inverted().map(mousePos))){ gc.setTransform(initialTransform); gc.setTransform(e.getInverse(), true); QPainterPath path; // Draw the ellipse path.addEllipse(QPointF(0, 0), extraE.semiMajor(), extraE.semiMinor()); drawPreview(gc, path); } QLineF radius(*handles()[1], *handles()[0]); radius.setAngle(fmod(radius.angle()+180.0,360.0)); if (extraE.set(radius.p1(), radius.p2(), initialTransform.inverted().map(mousePos))){ gc.setTransform(initialTransform); gc.setTransform(extraE.getInverse(), true); QPainterPath path; // Draw the ellipse path.addEllipse(QPointF(0, 0), extraE.semiMajor(), extraE.semiMinor()); drawPreview(gc, path); } QLineF radius2(*handles()[0], *handles()[1]); radius2.setAngle(fmod(radius2.angle()+180.0,360.0)); if (extraE.set(radius2.p1(), radius2.p2(), initialTransform.inverted().map(mousePos))){ gc.setTransform(initialTransform); gc.setTransform(extraE.getInverse(), true); QPainterPath path; // Draw the ellipse path.addEllipse(QPointF(0, 0), extraE.semiMajor(), extraE.semiMinor()); drawPreview(gc, path); } } } } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); } void FisheyePointAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { - if (assistantVisible==false){return;} + if (assistantVisible == false){ + return; + } QTransform initialTransform = converter->documentToWidgetTransform(); if (handles().size() == 2) { // just draw the axis gc.setTransform(initialTransform); QPainterPath path; path.moveTo(*handles()[0]); path.lineTo(*handles()[1]); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); return; } if (e.set(*handles()[0], *handles()[1], *handles()[2])) { // valid ellipse gc.setTransform(initialTransform); gc.setTransform(e.getInverse(), true); QPainterPath path; //path.moveTo(QPointF(-e.semiMajor(), -e.semiMinor())); path.lineTo(QPointF(e.semiMajor(), -e.semiMinor())); path.moveTo(QPointF(-e.semiMajor(), -e.semiMinor())); path.lineTo(QPointF(-e.semiMajor(), e.semiMinor())); //path.moveTo(QPointF(-e.semiMajor(), e.semiMinor())); path.lineTo(QPointF(e.semiMajor(), e.semiMinor())); path.moveTo(QPointF(e.semiMajor(), -e.semiMinor())); path.lineTo(QPointF(e.semiMajor(), e.semiMinor())); path.moveTo(QPointF(-(e.semiMajor()*3), -e.semiMinor())); path.lineTo(QPointF(-(e.semiMajor()*3), e.semiMinor())); path.moveTo(QPointF((e.semiMajor()*3), -e.semiMinor())); path.lineTo(QPointF((e.semiMajor()*3), e.semiMinor())); path.moveTo(QPointF(-e.semiMajor(), 0)); path.lineTo(QPointF(e.semiMajor(), 0)); //path.moveTo(QPointF(0, -e.semiMinor())); path.lineTo(QPointF(0, e.semiMinor())); // Draw the ellipse path.addEllipse(QPointF(0, 0), e.semiMajor(), e.semiMinor()); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); } } QRect FisheyePointAssistant::boundingRect() const { - if (handles().size() != 3) return KisPaintingAssistant::boundingRect(); + if (!isAssistantComplete()) { + return KisPaintingAssistant::boundingRect(); + } + if (e.set(*handles()[0], *handles()[1], *handles()[2])) { return e.boundingRect().adjusted(-(e.semiMajor()*2), -2, (e.semiMajor()*2), 2).toAlignedRect(); } else { return QRect(); } } QPointF FisheyePointAssistant::buttonPosition() const { return (*handles()[0] + *handles()[1]) * 0.5; } +bool FisheyePointAssistant::isAssistantComplete() const +{ + return handles().size() >= 3; +} FisheyePointAssistantFactory::FisheyePointAssistantFactory() { } FisheyePointAssistantFactory::~FisheyePointAssistantFactory() { } QString FisheyePointAssistantFactory::id() const { return "fisheye-point"; } QString FisheyePointAssistantFactory::name() const { return i18n("Fish Eye Point"); } KisPaintingAssistant* FisheyePointAssistantFactory::createPaintingAssistant() const { return new FisheyePointAssistant; } diff --git a/plugins/assistants/RulerAssistant/FisheyePointAssistant.h b/plugins/assistants/Assistants/FisheyePointAssistant.h similarity index 95% rename from plugins/assistants/RulerAssistant/FisheyePointAssistant.h rename to plugins/assistants/Assistants/FisheyePointAssistant.h index cc92bffb0e..3f7b49359d 100644 --- a/plugins/assistants/RulerAssistant/FisheyePointAssistant.h +++ b/plugins/assistants/Assistants/FisheyePointAssistant.h @@ -1,59 +1,63 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2014 Wolthera van Hövell tot Westerflier + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _FISHEYEPOINT_ASSISTANT_H_ #define _FISHEYEPOINT_ASSISTANT_H_ #include "kis_painting_assistant.h" #include "Ellipse.h" #include #include #include #include //class FisheyePoint; class FisheyePointAssistant : public KisPaintingAssistant { public: FisheyePointAssistant(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; //virtual void endStroke(); QPointF buttonPosition() const override; int numHandles() const override { return 3; } + + bool isAssistantComplete() const; + protected: QRect boundingRect() const override; void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override; void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt, const QPointF& strokeBegin); mutable Ellipse e; mutable Ellipse extraE; }; class FisheyePointAssistantFactory : public KisPaintingAssistantFactory { public: FisheyePointAssistantFactory(); ~FisheyePointAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/RulerAssistant/InfiniteRulerAssistant.cc b/plugins/assistants/Assistants/InfiniteRulerAssistant.cc similarity index 92% rename from plugins/assistants/RulerAssistant/InfiniteRulerAssistant.cc rename to plugins/assistants/Assistants/InfiniteRulerAssistant.cc index 8672ff9ce9..7ed5afc528 100644 --- a/plugins/assistants/RulerAssistant/InfiniteRulerAssistant.cc +++ b/plugins/assistants/Assistants/InfiniteRulerAssistant.cc @@ -1,153 +1,162 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2014 Wolthera van Hövell tot Westerflier + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "InfiniteRulerAssistant.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include InfiniteRulerAssistant::InfiniteRulerAssistant() : KisPaintingAssistant("infinite ruler", i18n("Infinite Ruler assistant")) { } QPointF InfiniteRulerAssistant::project(const QPointF& pt, const QPointF& strokeBegin) { - Q_ASSERT(handles().size() == 2); + Q_ASSERT(isAssistantComplete()); //code nicked from the perspective ruler. qreal dx = pt.x() - strokeBegin.x(), dy = pt.y() - strokeBegin.y(); if (dx * dx + dy * dy < 4.0) { // allow some movement before snapping return strokeBegin; } //dbgKrita<canvasWidget()->mapFromGlobal(QCursor::pos()); } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"< 1 && outline()==true && previewVisible==true) { + if (isAssistantComplete() && isSnappingActive() && previewVisible == true) { //don't draw if invalid. QTransform initialTransform = converter->documentToWidgetTransform(); QLineF snapLine= QLineF(initialTransform.map(*handles()[0]), initialTransform.map(*handles()[1])); QRect viewport= gc.viewport(); KisAlgebra2D::intersectLineRect(snapLine, viewport); QPainterPath path; path.moveTo(snapLine.p1()); path.lineTo(snapLine.p2()); drawPreview(gc, path);//and we draw the preview. } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); } void InfiniteRulerAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { - if (assistantVisible==false){return;} - if (handles().size() < 2) return; + if (assistantVisible == false || !isAssistantComplete()){ + return; + } QTransform initialTransform = converter->documentToWidgetTransform(); // Draw the line QPointF p1 = *handles()[0]; QPointF p2 = *handles()[1]; gc.setTransform(initialTransform); QPainterPath path; path.moveTo(p1); path.lineTo(p2); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); } QPointF InfiniteRulerAssistant::buttonPosition() const { return (*handles()[0]); } +bool InfiniteRulerAssistant::isAssistantComplete() const +{ + return handles().size() >= 2; +} + InfiniteRulerAssistantFactory::InfiniteRulerAssistantFactory() { } InfiniteRulerAssistantFactory::~InfiniteRulerAssistantFactory() { } QString InfiniteRulerAssistantFactory::id() const { return "infinite ruler"; } QString InfiniteRulerAssistantFactory::name() const { return i18n("Infinite Ruler"); } KisPaintingAssistant* InfiniteRulerAssistantFactory::createPaintingAssistant() const { return new InfiniteRulerAssistant; } diff --git a/plugins/assistants/RulerAssistant/InfiniteRulerAssistant.h b/plugins/assistants/Assistants/InfiniteRulerAssistant.h similarity index 95% rename from plugins/assistants/RulerAssistant/InfiniteRulerAssistant.h rename to plugins/assistants/Assistants/InfiniteRulerAssistant.h index ff5562beea..8a5eb128d7 100644 --- a/plugins/assistants/RulerAssistant/InfiniteRulerAssistant.h +++ b/plugins/assistants/Assistants/InfiniteRulerAssistant.h @@ -1,57 +1,60 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2014 Wolthera van Hövell tot Westerflier + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _INFINITERULER_ASSISTANT_H_ #define _INFINITERULER_ASSISTANT_H_ #include "kis_painting_assistant.h" #include #include #include #include /* Design: */ class InfiniteRuler; class InfiniteRulerAssistant : public KisPaintingAssistant { public: InfiniteRulerAssistant(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; //virtual void endStroke(); QPointF buttonPosition() const override; int numHandles() const override { return 2; } + bool isAssistantComplete() const; + protected: void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override; void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt, const QPointF& strokeBegin); }; class InfiniteRulerAssistantFactory : public KisPaintingAssistantFactory { public: InfiniteRulerAssistantFactory(); ~InfiniteRulerAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/RulerAssistant/KisRulerAssistantTool.action b/plugins/assistants/Assistants/KisAssistantTool.action similarity index 58% rename from plugins/assistants/RulerAssistant/KisRulerAssistantTool.action rename to plugins/assistants/Assistants/KisAssistantTool.action index fc5271ac50..fe7e843908 100644 --- a/plugins/assistants/RulerAssistant/KisRulerAssistantTool.action +++ b/plugins/assistants/Assistants/KisAssistantTool.action @@ -1,6 +1,6 @@ - - Ruler Assistant Tool + + Assistant Tool diff --git a/plugins/assistants/RulerAssistant/ParallelRulerAssistant.cc b/plugins/assistants/Assistants/ParallelRulerAssistant.cc similarity index 83% rename from plugins/assistants/RulerAssistant/ParallelRulerAssistant.cc rename to plugins/assistants/Assistants/ParallelRulerAssistant.cc index 8fe35eb032..3579927e05 100644 --- a/plugins/assistants/RulerAssistant/ParallelRulerAssistant.cc +++ b/plugins/assistants/Assistants/ParallelRulerAssistant.cc @@ -1,161 +1,169 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2014 Wolthera van Hövell tot Westerflier + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ParallelRulerAssistant.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include ParallelRulerAssistant::ParallelRulerAssistant() - : KisPaintingAssistant("parallel ruler", i18n("Parallel Ruler assistant")) + : KisPaintingAssistant("parallel ruler", i18n("Parallel Ruler assistant")) { } QPointF ParallelRulerAssistant::project(const QPointF& pt, const QPointF& strokeBegin) { - Q_ASSERT(handles().size() == 2); + Q_ASSERT(isAssistantComplete()); + //code nicked from the perspective ruler. - qreal - dx = pt.x() - strokeBegin.x(), - dy = pt.y() - strokeBegin.y(); - if (dx * dx + dy * dy < 4.0) { - // allow some movement before snapping - return strokeBegin; - } + qreal dx = pt.x() - strokeBegin.x(); + qreal dy = pt.y() - strokeBegin.y(); + + if (dx * dx + dy * dy < 4.0) { + return strokeBegin; // allow some movement before snapping + } + //dbgKrita<canvasWidget()->mapFromGlobal(QCursor::pos()); } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"< 1 && outline()==true && previewVisible==true) { + if (isAssistantComplete() && isSnappingActive() && previewVisible==true) { //don't draw if invalid. QTransform initialTransform = converter->documentToWidgetTransform(); QLineF snapLine= QLineF(initialTransform.map(*handles()[0]), initialTransform.map(*handles()[1])); QPointF translation = (initialTransform.map(*handles()[0])-mousePos)*-1.0; snapLine= snapLine.translated(translation); - + QRect viewport= gc.viewport(); KisAlgebra2D::intersectLineRect(snapLine, viewport); QPainterPath path; path.moveTo(snapLine.p1()); path.lineTo(snapLine.p2()); drawPreview(gc, path);//and we draw the preview. } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); } void ParallelRulerAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { - if (assistantVisible==false){return;} - if (handles().size() < 2) return; + if (assistantVisible == false || !isAssistantComplete()){ + return; + } QTransform initialTransform = converter->documentToWidgetTransform(); // Draw the line QPointF p1 = *handles()[0]; QPointF p2 = *handles()[1]; gc.setTransform(initialTransform); QPainterPath path; path.moveTo(p1); path.lineTo(p2); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); } QPointF ParallelRulerAssistant::buttonPosition() const { return (*handles()[0] + *handles()[1]) * 0.5; } +bool ParallelRulerAssistant::isAssistantComplete() const +{ + return handles().size() >= 2; +} + ParallelRulerAssistantFactory::ParallelRulerAssistantFactory() { } ParallelRulerAssistantFactory::~ParallelRulerAssistantFactory() { } QString ParallelRulerAssistantFactory::id() const { return "parallel ruler"; } QString ParallelRulerAssistantFactory::name() const { return i18n("Parallel Ruler"); } KisPaintingAssistant* ParallelRulerAssistantFactory::createPaintingAssistant() const { return new ParallelRulerAssistant; } diff --git a/plugins/assistants/RulerAssistant/ParallelRulerAssistant.h b/plugins/assistants/Assistants/ParallelRulerAssistant.h similarity index 95% rename from plugins/assistants/RulerAssistant/ParallelRulerAssistant.h rename to plugins/assistants/Assistants/ParallelRulerAssistant.h index 9a37b40559..2c7cf19e21 100644 --- a/plugins/assistants/RulerAssistant/ParallelRulerAssistant.h +++ b/plugins/assistants/Assistants/ParallelRulerAssistant.h @@ -1,57 +1,60 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2014 Wolthera van Hövell tot Westerflier + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _PARALLELRULER_ASSISTANT_H_ #define _PARALLELRULER_ASSISTANT_H_ #include "kis_painting_assistant.h" #include #include #include #include /* Design: */ class ParallelRuler; class ParallelRulerAssistant : public KisPaintingAssistant { public: ParallelRulerAssistant(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; //virtual void endStroke(); QPointF buttonPosition() const override; int numHandles() const override { return 2; } + bool isAssistantComplete() const; + protected: void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override; void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt, const QPointF& strokeBegin); }; class ParallelRulerAssistantFactory : public KisPaintingAssistantFactory { public: ParallelRulerAssistantFactory(); ~ParallelRulerAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc b/plugins/assistants/Assistants/PerspectiveAssistant.cc similarity index 76% rename from plugins/assistants/RulerAssistant/PerspectiveAssistant.cc rename to plugins/assistants/Assistants/PerspectiveAssistant.cc index 837ee546a7..7f5811b725 100644 --- a/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc +++ b/plugins/assistants/Assistants/PerspectiveAssistant.cc @@ -1,420 +1,463 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "PerspectiveAssistant.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include PerspectiveAssistant::PerspectiveAssistant(QObject *parent) - : KisAbstractPerspectiveGrid(parent) - , KisPaintingAssistant("perspective", i18n("Perspective assistant")) + : KisAbstractPerspectiveGrid(parent) + , KisPaintingAssistant("perspective", i18n("Perspective assistant")) { } // squared distance from a point to a line inline qreal distsqr(const QPointF& pt, const QLineF& line) { // distance = |(p2 - p1) x (p1 - pt)| / |p2 - p1| // magnitude of (p2 - p1) x (p1 - pt) const qreal cross = (line.dx() * (line.y1() - pt.y()) - line.dy() * (line.x1() - pt.x())); return cross * cross / (line.dx() * line.dx() + line.dy() * line.dy()); } QPointF PerspectiveAssistant::project(const QPointF& pt, const QPointF& strokeBegin) { const static QPointF nullPoint(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); - Q_ASSERT(handles().size() == 4); + + Q_ASSERT(isAssistantComplete()); + if (m_snapLine.isNull()) { QPolygonF poly; QTransform transform; - if (!getTransform(poly, transform)) return nullPoint; - // avoid problems with multiple assistants: only snap if starting in the grid - if (!poly.containsPoint(strokeBegin, Qt::OddEvenFill)) return nullPoint; - const qreal - dx = pt.x() - strokeBegin.x(), - dy = pt.y() - strokeBegin.y(); + if (!getTransform(poly, transform)) { + return nullPoint; + } + + if (!poly.containsPoint(strokeBegin, Qt::OddEvenFill)) { + return nullPoint; // avoid problems with multiple assistants: only snap if starting in the grid + } + + + const qreal dx = pt.x() - strokeBegin.x(); + const qreal dy = pt.y() - strokeBegin.y(); + if (dx * dx + dy * dy < 4.0) { - // allow some movement before snapping - return strokeBegin; + return strokeBegin; // allow some movement before snapping } // construct transformation bool invertible; const QTransform inverse = transform.inverted(&invertible); - if (!invertible) return nullPoint; // shouldn't happen + if (!invertible) { + return nullPoint; // shouldn't happen + } + // figure out which direction to go const QPointF start = inverse.map(strokeBegin); - const QLineF - verticalLine = QLineF(strokeBegin, transform.map(start + QPointF(0, 1))), - horizontalLine = QLineF(strokeBegin, transform.map(start + QPointF(1, 0))); + const QLineF verticalLine = QLineF(strokeBegin, transform.map(start + QPointF(0, 1))); + const QLineF horizontalLine = QLineF(strokeBegin, transform.map(start + QPointF(1, 0))); + // determine whether the horizontal or vertical line is closer to the point m_snapLine = distsqr(pt, verticalLine) < distsqr(pt, horizontalLine) ? verticalLine : horizontalLine; } // snap to line const qreal - dx = m_snapLine.dx(), - dy = m_snapLine.dy(), - dx2 = dx * dx, - dy2 = dy * dy, - invsqrlen = 1.0 / (dx2 + dy2); + dx = m_snapLine.dx(), + dy = m_snapLine.dy(), + dx2 = dx * dx, + dy2 = dy * dy, + invsqrlen = 1.0 / (dx2 + dy2); QPointF r(dx2 * pt.x() + dy2 * m_snapLine.x1() + dx * dy * (pt.y() - m_snapLine.y1()), dx2 * m_snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - m_snapLine.x1())); + r *= invsqrlen; return r; } QPointF PerspectiveAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin) { return project(pt, strokeBegin); } void PerspectiveAssistant::endStroke() { m_snapLine = QLineF(); } bool PerspectiveAssistant::contains(const QPointF& pt) const { QPolygonF poly; if (!quad(poly)) return false; return poly.containsPoint(pt, Qt::OddEvenFill); } inline qreal lengthSquared(const QPointF& vector) { return vector.x() * vector.x() + vector.y() * vector.y(); } inline qreal localScale(const QTransform& transform, QPointF pt) { -// const qreal epsilon = 1e-5, epsilonSquared = epsilon * epsilon; -// qreal xSizeSquared = lengthSquared(transform.map(pt + QPointF(epsilon, 0.0)) - orig) / epsilonSquared; -// qreal ySizeSquared = lengthSquared(transform.map(pt + QPointF(0.0, epsilon)) - orig) / epsilonSquared; -// xSizeSquared /= lengthSquared(transform.map(QPointF(0.0, pt.y())) - transform.map(QPointF(1.0, pt.y()))); -// ySizeSquared /= lengthSquared(transform.map(QPointF(pt.x(), 0.0)) - transform.map(QPointF(pt.x(), 1.0))); -// when taking the limit epsilon->0: -// xSizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4 -// ySizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4 -// xSize*ySize=(abs(m13*x+m33)*abs(m13*x+m33+m23)*abs(m23*y+m33)*abs(m23*y+m33+m13))/(m23*y+m13*x+m33)^4 + // const qreal epsilon = 1e-5, epsilonSquared = epsilon * epsilon; + // qreal xSizeSquared = lengthSquared(transform.map(pt + QPointF(epsilon, 0.0)) - orig) / epsilonSquared; + // qreal ySizeSquared = lengthSquared(transform.map(pt + QPointF(0.0, epsilon)) - orig) / epsilonSquared; + // xSizeSquared /= lengthSquared(transform.map(QPointF(0.0, pt.y())) - transform.map(QPointF(1.0, pt.y()))); + // ySizeSquared /= lengthSquared(transform.map(QPointF(pt.x(), 0.0)) - transform.map(QPointF(pt.x(), 1.0))); + // when taking the limit epsilon->0: + // xSizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4 + // ySizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4 + // xSize*ySize=(abs(m13*x+m33)*abs(m13*x+m33+m23)*abs(m23*y+m33)*abs(m23*y+m33+m13))/(m23*y+m13*x+m33)^4 const qreal x = transform.m13() * pt.x(), - y = transform.m23() * pt.y(), - a = x + transform.m33(), - b = y + transform.m33(), - c = x + y + transform.m33(), - d = c * c; + y = transform.m23() * pt.y(), + a = x + transform.m33(), + b = y + transform.m33(), + c = x + y + transform.m33(), + d = c * c; return fabs(a*(a + transform.m23())*b*(b + transform.m13()))/(d * d); } // returns the reciprocal of the maximum local scale at the points (0,0),(0,1),(1,0),(1,1) inline qreal inverseMaxLocalScale(const QTransform& transform) { const qreal a = fabs((transform.m33() + transform.m13()) * (transform.m33() + transform.m23())), - b = fabs((transform.m33()) * (transform.m13() + transform.m33() + transform.m23())), - d00 = transform.m33() * transform.m33(), - d11 = (transform.m33() + transform.m23() + transform.m13())*(transform.m33() + transform.m23() + transform.m13()), - s0011 = qMin(d00, d11) / a, - d10 = (transform.m33() + transform.m13()) * (transform.m33() + transform.m13()), - d01 = (transform.m33() + transform.m23()) * (transform.m33() + transform.m23()), - s1001 = qMin(d10, d01) / b; + b = fabs((transform.m33()) * (transform.m13() + transform.m33() + transform.m23())), + d00 = transform.m33() * transform.m33(), + d11 = (transform.m33() + transform.m23() + transform.m13())*(transform.m33() + transform.m23() + transform.m13()), + s0011 = qMin(d00, d11) / a, + d10 = (transform.m33() + transform.m13()) * (transform.m33() + transform.m13()), + d01 = (transform.m33() + transform.m23()) * (transform.m33() + transform.m23()), + s1001 = qMin(d10, d01) / b; return qMin(s0011, s1001); } qreal PerspectiveAssistant::distance(const QPointF& pt) const { QPolygonF poly; QTransform transform; - if (!getTransform(poly, transform)) return 1.0; + + if (!getTransform(poly, transform)) { + return 1.0; + } + bool invertible; QTransform inverse = transform.inverted(&invertible); - if (!invertible) return 1.0; + + if (!invertible) { + return 1.0; + } + if (inverse.m13() * pt.x() + inverse.m23() * pt.y() + inverse.m33() == 0.0) { - // point at infinity - return 0.0; + return 0.0; // point at infinity } + return localScale(transform, inverse.map(pt)) * inverseMaxLocalScale(transform); } // draw a vanishing point marker inline QPainterPath drawX(const QPointF& pt) { QPainterPath path; path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0)); path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0)); return path; } void PerspectiveAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { gc.save(); gc.resetTransform(); QTransform initialTransform = converter->documentToWidgetTransform(); //QTransform reverseTransform = converter->widgetToDocument(); QPolygonF poly; QTransform transform; // unused, but computed for caching purposes if (getTransform(poly, transform) && assistantVisible==true) { // draw vanishing points QPointF intersection(0, 0); if (fmod(QLineF(poly[0], poly[1]).angle(), 180.0)>=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0 || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) { if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) { drawPath(gc, drawX(initialTransform.map(intersection))); } } if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0 || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){ if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) { drawPath(gc, drawX(initialTransform.map(intersection))); } } } - if (outline()==true && getTransform(poly, transform) && previewVisible==true){ + if (isSnappingActive() && getTransform(poly, transform) && previewVisible==true){ //find vanishing point, find mouse, draw line between both. QPainterPath path2; QPointF intersection(0, 0);//this is the position of the vanishing point. QPointF mousePos(0,0); QLineF snapLine; QRect viewport= gc.viewport(); QRect bounds; if (canvas){ - //simplest, cheapest way to get the mouse-position// + //simplest, cheapest way to get the mouse-position mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); } else { - //...of course, you need to have access to a canvas-widget for that.// - mousePos = QCursor::pos();//this'll give an offset// + //...of course, you need to have access to a canvas-widget for that. + mousePos = QCursor::pos(); // this'll give an offset dbgFile<<"canvas does not exist, you may have passed arguments incorrectly:"<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0 || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) { if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) { - intersectTransformed = initialTransform.map(intersection); + intersectTransformed = initialTransform.map(intersection); snapLine = QLineF(intersectTransformed, mousePos); KisAlgebra2D::intersectLineRect(snapLine, viewport); bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); QPainterPath path; + if (bounds.contains(intersectTransformed.toPoint())){ path2.moveTo(intersectTransformed); path2.lineTo(snapLine.p1()); } else { path2.moveTo(snapLine.p1()); path2.lineTo(snapLine.p2()); } } } if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0 || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){ if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) { - intersectTransformed = initialTransform.map(intersection); + intersectTransformed = initialTransform.map(intersection); snapLine = QLineF(intersectTransformed, mousePos); KisAlgebra2D::intersectLineRect(snapLine, viewport); bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); QPainterPath path; + if (bounds.contains(intersectTransformed.toPoint())){ path2.moveTo(intersectTransformed); path2.lineTo(snapLine.p1()); } else { path2.moveTo(snapLine.p1()); path2.lineTo(snapLine.p2()); } } } drawPreview(gc, path2); } } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached,canvas, assistantVisible, previewVisible); } void PerspectiveAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { - if (assistantVisible==false) { + if (assistantVisible == false) { return; } + gc.setTransform(converter->documentToWidgetTransform()); QPolygonF poly; QTransform transform; + if (!getTransform(poly, transform)) { // color red for an invalid transform, but not for an incomplete one - if(handles().size() == 4) - { + if(isAssistantComplete()) { gc.setPen(QColor(255, 0, 0, 125)); gc.drawPolygon(poly); } else { QPainterPath path; path.addPolygon(poly); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); } } else { gc.setPen(QColor(0, 0, 0, 125)); gc.setTransform(transform, true); QPainterPath path; for (int y = 0; y <= 8; ++y) { path.moveTo(QPointF(0.0, y * 0.125)); path.lineTo(QPointF(1.0, y * 0.125)); } for (int x = 0; x <= 8; ++x) { path.moveTo(QPointF(x * 0.125, 0.0)); path.lineTo(QPointF(x * 0.125, 1.0)); } - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); } } QPointF PerspectiveAssistant::buttonPosition() const { QPointF centroid(0, 0); - for (int i = 0; i < 4; ++i) centroid += *handles()[i]; + for (int i = 0; i < 4; ++i) { + centroid += *handles()[i]; + } + return centroid * 0.25; } template int sign(T a) { return (a > 0) - (a < 0); } // perpendicular dot product inline qreal pdot(const QPointF& a, const QPointF& b) { return a.x() * b.y() - a.y() * b.x(); } bool PerspectiveAssistant::quad(QPolygonF& poly) const { - for (int i = 0; i < handles().size(); ++i) + for (int i = 0; i < handles().size(); ++i) { poly.push_back(*handles()[i]); - if (handles().size() != 4) { + } + + if (!isAssistantComplete()) { return false; } + int sum = 0; int signs[4]; + for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); int k = (j == 3) ? 0 : (j + 1); signs[i] = sign(pdot(poly[j] - poly[i], poly[k] - poly[j])); sum += signs[i]; } + if (sum == 0) { // complex (crossed) for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); if (signs[i] * signs[j] == -1) { // opposite signs: uncross std::swap(poly[i], poly[j]); return true; } } // okay, maybe it's just a line return false; } else if (sum != 4 && sum != -4) { // concave, or a triangle if (sum == 2 || sum == -2) { // concave, let's return a triangle instead for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); if (signs[i] != sign(sum)) { // wrong sign: drop the inside node poly.remove(j); return false; } } } return false; } // convex return true; } bool PerspectiveAssistant::getTransform(QPolygonF& poly, QTransform& transform) const { - if (m_cachedPolygon.size() != 0 && handles().size() == 4) { + if (m_cachedPolygon.size() != 0 && isAssistantComplete()) { for (int i = 0; i <= 4; ++i) { if (i == 4) { poly = m_cachedPolygon; transform = m_cachedTransform; return m_cacheValid; } if (m_cachedPoints[i] != *handles()[i]) break; } } + m_cachedPolygon.clear(); m_cacheValid = false; + if (!quad(poly)) { m_cachedPolygon = poly; return false; } + if (!QTransform::squareToQuad(poly, transform)) { qWarning("Failed to create perspective mapping"); return false; } + for (int i = 0; i < 4; ++i) { m_cachedPoints[i] = *handles()[i]; } + m_cachedPolygon = poly; m_cachedTransform = transform; m_cacheValid = true; return true; } +bool PerspectiveAssistant::isAssistantComplete() const +{ + return handles().size() >= 4; // specify 4 corners to make assistant complete +} + + + PerspectiveAssistantFactory::PerspectiveAssistantFactory() { } PerspectiveAssistantFactory::~PerspectiveAssistantFactory() { } QString PerspectiveAssistantFactory::id() const { return "perspective"; } QString PerspectiveAssistantFactory::name() const { return i18n("Perspective"); } KisPaintingAssistant* PerspectiveAssistantFactory::createPaintingAssistant() const { return new PerspectiveAssistant; } diff --git a/plugins/assistants/RulerAssistant/PerspectiveAssistant.h b/plugins/assistants/Assistants/PerspectiveAssistant.h similarity index 96% rename from plugins/assistants/RulerAssistant/PerspectiveAssistant.h rename to plugins/assistants/Assistants/PerspectiveAssistant.h index c843dadfaa..db5755184d 100644 --- a/plugins/assistants/RulerAssistant/PerspectiveAssistant.h +++ b/plugins/assistants/Assistants/PerspectiveAssistant.h @@ -1,70 +1,75 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _PERSPECTIVE_ASSISTANT_H_ #define _PERSPECTIVE_ASSISTANT_H_ #include "kis_abstract_perspective_grid.h" #include "kis_painting_assistant.h" #include #include #include #include class PerspectiveAssistant : public KisAbstractPerspectiveGrid, public KisPaintingAssistant { Q_OBJECT public: PerspectiveAssistant(QObject * parent = 0); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; void endStroke() override; QPointF buttonPosition() const override; int numHandles() const override { return 4; } void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override; bool contains(const QPointF& point) const override; qreal distance(const QPointF& point) const override; + + + bool isAssistantComplete() const; + protected: void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt, const QPointF& strokeBegin); // creates the convex hull, returns false if it's not a quadrilateral bool quad(QPolygonF& out) const; // finds the transform from perspective coordinates (a unit square) to the document bool getTransform(QPolygonF& polyOut, QTransform& transformOut) const; // which direction to snap to (in transformed coordinates) QLineF m_snapLine; // cached information mutable QTransform m_cachedTransform; mutable QPolygonF m_cachedPolygon; mutable QPointF m_cachedPoints[4]; mutable bool m_cacheValid; }; class PerspectiveAssistantFactory : public KisPaintingAssistantFactory { public: PerspectiveAssistantFactory(); ~PerspectiveAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/RulerAssistant/Ruler.cc b/plugins/assistants/Assistants/Ruler.cc similarity index 100% rename from plugins/assistants/RulerAssistant/Ruler.cc rename to plugins/assistants/Assistants/Ruler.cc diff --git a/plugins/assistants/RulerAssistant/Ruler.h b/plugins/assistants/Assistants/Ruler.h similarity index 100% rename from plugins/assistants/RulerAssistant/Ruler.h rename to plugins/assistants/Assistants/Ruler.h diff --git a/plugins/assistants/RulerAssistant/RulerAssistant.cc b/plugins/assistants/Assistants/RulerAssistant.cc similarity index 88% rename from plugins/assistants/RulerAssistant/RulerAssistant.cc rename to plugins/assistants/Assistants/RulerAssistant.cc index 9bc7ed659e..88e47b5e0d 100644 --- a/plugins/assistants/RulerAssistant/RulerAssistant.cc +++ b/plugins/assistants/Assistants/RulerAssistant.cc @@ -1,159 +1,166 @@ /* * Copyright (c) 2008 Cyrille Berger + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "RulerAssistant.h" #include "kis_debug.h" #include #include #include #include #include #include #include RulerAssistant::RulerAssistant() : KisPaintingAssistant("ruler", i18n("Ruler assistant")) { } QPointF RulerAssistant::project(const QPointF& pt) const { - Q_ASSERT(handles().size() == 2); + Q_ASSERT(isAssistantComplete()); QPointF pt1 = *handles()[0]; QPointF pt2 = *handles()[1]; QPointF a = pt - pt1; QPointF u = pt2 - pt1; qreal u_norm = sqrt(u.x() * u.x() + u.y() * u.y()); if(u_norm == 0) return pt; u /= u_norm; double t = a.x() * u.x() + a.y() * u.y(); if(t < 0.0) return pt1; if(t > u_norm) return pt2; return t * u + pt1; } QPointF RulerAssistant::adjustPosition(const QPointF& pt, const QPointF& /*strokeBegin*/) { return project(pt); } inline double angle(const QPointF& p1, const QPointF& p2) { return atan2(p2.y() - p1.y(), p2.x() - p1.x()); } inline double norm2(const QPointF& p) { return sqrt(p.x() * p.x() + p.y() * p.y()); } void RulerAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { gc.save(); gc.resetTransform(); QPointF mousePos; if (canvas){ //simplest, cheapest way to get the mouse-position// mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"< 1) { - //don't draw if invalid. + // don't draw if invalid + if (isAssistantComplete()) { QTransform initialTransform = converter->documentToWidgetTransform(); // first we find the path that our point create. QPointF p1 = *handles()[0]; QPointF p2 = *handles()[1]; gc.setTransform(initialTransform); QPainterPath path; path.moveTo(p1); path.lineTo(p2); //then we use this path to check the bounding rectangle// - if (outline()==true && path.boundingRect().contains(initialTransform.inverted().map(mousePos)) && previewVisible==true){ + if (isSnappingActive() && path.boundingRect().contains(initialTransform.inverted().map(mousePos)) && previewVisible==true){ drawPreview(gc, path);//and we draw the preview. } } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); } void RulerAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { - if (assistantVisible==false){return;} - if (handles().size() < 2) return; + if (assistantVisible == false || !isAssistantComplete()){ + return; + } QTransform initialTransform = converter->documentToWidgetTransform(); // Draw the line QPointF p1 = *handles()[0]; QPointF p2 = *handles()[1]; gc.setTransform(initialTransform); QPainterPath path; path.moveTo(p1); path.lineTo(p2); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); } QPointF RulerAssistant::buttonPosition() const { return (*handles()[0] + *handles()[1]) * 0.5; } +bool RulerAssistant::isAssistantComplete() const +{ + return handles().size() >= 2; +} + RulerAssistantFactory::RulerAssistantFactory() { } RulerAssistantFactory::~RulerAssistantFactory() { } QString RulerAssistantFactory::id() const { return "ruler"; } QString RulerAssistantFactory::name() const { return i18n("Ruler"); } KisPaintingAssistant* RulerAssistantFactory::createPaintingAssistant() const { return new RulerAssistant; } diff --git a/plugins/assistants/RulerAssistant/RulerAssistant.h b/plugins/assistants/Assistants/RulerAssistant.h similarity index 94% rename from plugins/assistants/RulerAssistant/RulerAssistant.h rename to plugins/assistants/Assistants/RulerAssistant.h index 39bae336ee..08c8e2b837 100644 --- a/plugins/assistants/RulerAssistant/RulerAssistant.h +++ b/plugins/assistants/Assistants/RulerAssistant.h @@ -1,49 +1,52 @@ /* * Copyright (c) 2008 Cyrille Berger + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _RULER_ASSISTANT_H_ #define _RULER_ASSISTANT_H_ #include "kis_painting_assistant.h" class Ruler; class RulerAssistant : public KisPaintingAssistant { public: RulerAssistant(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; QPointF buttonPosition() const override; int numHandles() const override { return 2; } + bool isAssistantComplete() const; + protected: void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible=true, bool previewVisible=true) override; void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt) const; }; class RulerAssistantFactory : public KisPaintingAssistantFactory { public: RulerAssistantFactory(); ~RulerAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/RulerAssistant/SplineAssistant.cc b/plugins/assistants/Assistants/SplineAssistant.cc similarity index 65% rename from plugins/assistants/RulerAssistant/SplineAssistant.cc rename to plugins/assistants/Assistants/SplineAssistant.cc index e81ed2caf9..a6cf50a5b1 100644 --- a/plugins/assistants/RulerAssistant/SplineAssistant.cc +++ b/plugins/assistants/Assistants/SplineAssistant.cc @@ -1,187 +1,224 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "SplineAssistant.h" #include #include #include #include #include #include #include "kis_debug.h" #include #include #include SplineAssistant::SplineAssistant() - : KisPaintingAssistant("spline", i18n("Spline assistant")) + : KisPaintingAssistant("spline", i18n("Spline assistant")) { } // parametric form of a cubic spline (B(t) = (1-t)^3 P0 + 3 (1-t)^2 t P1 + 3 (1-t) t^2 P2 + t^3 P3) inline QPointF B(qreal t, const QPointF& P0, const QPointF& P1, const QPointF& P2, const QPointF& P3) { - const qreal - tp = 1 - t, - tp2 = tp * tp, - t2 = t * t; - return - ( tp2 * tp) * P0 + - (3 * tp2 * t ) * P1 + - (3 * tp * t2) * P2 + - ( t * t2) * P3; + const qreal tp = 1 - t; + const qreal tp2 = tp * tp; + const qreal t2 = t * t; + + return ( tp2 * tp) * P0 + + (3 * tp2 * t ) * P1 + + (3 * tp * t2) * P2 + + ( t * t2) * P3; } // squared distance from a point on the spline to given point: we want to minimize this inline qreal D(qreal t, const QPointF& P0, const QPointF& P1, const QPointF& P2, const QPointF& P3, const QPointF& p) { const qreal - tp = 1 - t, - tp2 = tp * tp, - t2 = t * t, - a = tp2 * tp, - b = 3 * tp2 * t, - c = 3 * tp * t2, - d = t * t2, - x_dist = a*P0.x() + b*P1.x() + c*P2.x() + d*P3.x() - p.x(), - y_dist = a*P0.y() + b*P1.y() + c*P2.y() + d*P3.y() - p.y(); + tp = 1 - t, + tp2 = tp * tp, + t2 = t * t, + a = tp2 * tp, + b = 3 * tp2 * t, + c = 3 * tp * t2, + d = t * t2, + x_dist = a*P0.x() + b*P1.x() + c*P2.x() + d*P3.x() - p.x(), + y_dist = a*P0.y() + b*P1.y() + c*P2.y() + d*P3.y() - p.y(); + return x_dist * x_dist + y_dist * y_dist; } QPointF SplineAssistant::project(const QPointF& pt) const { - Q_ASSERT(handles().size() == 4); + Q_ASSERT(isAssistantComplete()); // minimize d(t), but keep t in the same neighbourhood as before (unless starting a new stroke) // (this is a rather inefficient method) qreal min_t = std::numeric_limits::max(); qreal d_min_t = std::numeric_limits::max(); + for (qreal t = 0; t <= 1; t += 1e-3) { qreal d_t = D(t, *handles()[0], *handles()[2], *handles()[3], *handles()[1], pt); if (d_t < d_min_t) { d_min_t = d_t; min_t = t; } } return B(min_t, *handles()[0], *handles()[2], *handles()[3], *handles()[1]); } QPointF SplineAssistant::adjustPosition(const QPointF& pt, const QPointF& /*strokeBegin*/) { return project(pt); } void SplineAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { gc.save(); gc.resetTransform(); QPoint mousePos; if (canvas){ //simplest, cheapest way to get the mouse-position// mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); + m_canvas = canvas; } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist in spline, you may have passed arguments incorrectly:"< 1) { - QTransform initialTransform = converter->documentToWidgetTransform(); + if (handles().size() > 1) { - // first we find the path that our point create. + QTransform initialTransform = converter->documentToWidgetTransform(); + // first we find the path that our point create. QPointF pts[4]; pts[0] = *handles()[0]; pts[1] = *handles()[1]; pts[2] = (handles().size() >= 3) ? (*handles()[2]) : (*handles()[0]); pts[3] = (handles().size() >= 4) ? (*handles()[3]) : (handles().size() >= 3) ? (*handles()[2]) : (*handles()[1]); gc.setTransform(initialTransform); + // Draw the spline QPainterPath path; path.moveTo(pts[0]); path.cubicTo(pts[2], pts[3], pts[1]); //then we use this path to check the bounding rectangle// - if (outline()==true && path.boundingRect().contains(initialTransform.inverted().map(mousePos)) && previewVisible==true){ + if (isSnappingActive() && path.boundingRect().contains(initialTransform.inverted().map(mousePos)) && previewVisible==true){ drawPreview(gc, path);//and we draw the preview. } } gc.restore(); - - KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); + // there is some odd rectangle that is getting rendered when there is only one point, so don't start rendering the line until after 2 + // this issue only exists with this spline assistant...none of the others + if (handles().size() > 2) { + KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); + } } void SplineAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { - if (assistantVisible==false){return;} - if (handles().size() < 2) return; + if (assistantVisible == false || handles().size() < 2 ){ + return; + } QTransform initialTransform = converter->documentToWidgetTransform(); QPointF pts[4]; pts[0] = *handles()[0]; pts[1] = *handles()[1]; pts[2] = (handles().size() >= 3) ? (*handles()[2]) : (*handles()[0]); pts[3] = (handles().size() >= 4) ? (*handles()[3]) : (handles().size() >= 3) ? (*handles()[2]) : (*handles()[1]); gc.setTransform(initialTransform); - gc.setPen(QColor(0, 0, 0, 75)); - // Draw control lines - gc.drawLine(pts[0], pts[2]); - if (handles().size() >= 4) gc.drawLine(pts[1], pts[3]); - gc.setPen(QColor(0, 0, 0, 125)); + + + { // Draw bezier handles control lines only if we are editing the assistant + gc.save(); + QColor assistantColor = m_canvas->paintingAssistantsDecoration()->assistantsColor(); + QPen bezierlinePen(assistantColor); + bezierlinePen.setStyle(Qt::DotLine); + bezierlinePen.setWidth(1); + + if (m_canvas->paintingAssistantsDecoration()->isEditingAssistants()) { + + if (!isSnappingActive()) { + bezierlinePen.setColor(QColor(assistantColor.red(), assistantColor.green(), assistantColor.blue(), assistantColor.alpha()*.2)); + } + + gc.setPen(bezierlinePen); + gc.drawLine(pts[0], pts[2]); + + if (isAssistantComplete()) { + gc.drawLine(pts[1], pts[3]); + } + gc.setPen(QColor(0, 0, 0, 125)); + } + gc.restore(); + } + + // Draw the spline QPainterPath path; path.moveTo(pts[0]); path.cubicTo(pts[2], pts[3], pts[1]); - drawPath(gc, path, snapping()); + drawPath(gc, path, isSnappingActive()); + + } QPointF SplineAssistant::buttonPosition() const { return B(0.5, *handles()[0], *handles()[2], *handles()[3], *handles()[1]); } +bool SplineAssistant::isAssistantComplete() const +{ + return handles().size() >= 4; // specify 4 corners to make assistant complete +} + SplineAssistantFactory::SplineAssistantFactory() { } SplineAssistantFactory::~SplineAssistantFactory() { } QString SplineAssistantFactory::id() const { return "spline"; } QString SplineAssistantFactory::name() const { return i18n("Spline"); } KisPaintingAssistant* SplineAssistantFactory::createPaintingAssistant() const { return new SplineAssistant; } diff --git a/plugins/assistants/RulerAssistant/SplineAssistant.h b/plugins/assistants/Assistants/SplineAssistant.h similarity index 89% rename from plugins/assistants/RulerAssistant/SplineAssistant.h rename to plugins/assistants/Assistants/SplineAssistant.h index acab43e22c..f9cb2fb3b4 100644 --- a/plugins/assistants/RulerAssistant/SplineAssistant.h +++ b/plugins/assistants/Assistants/SplineAssistant.h @@ -1,49 +1,55 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _SPLINE_ASSISTANT_H_ #define _SPLINE_ASSISTANT_H_ #include "kis_painting_assistant.h" #include class SplineAssistant : public KisPaintingAssistant { public: SplineAssistant(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; QPointF buttonPosition() const override; int numHandles() const override { return 4; } + bool isAssistantComplete() const; + protected: void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible=true, bool previewVisible=true) override; void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt) const; + + /// used for getting the decoration so the bezier handles aren't drawn while editing + KisCanvas2* m_canvas; }; class SplineAssistantFactory : public KisPaintingAssistantFactory { public: SplineAssistantFactory(); ~SplineAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/Assistants/VanishingPointAssistant.cc b/plugins/assistants/Assistants/VanishingPointAssistant.cc new file mode 100644 index 0000000000..0c0a8af567 --- /dev/null +++ b/plugins/assistants/Assistants/VanishingPointAssistant.cc @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2008 Cyrille Berger + * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2014 Wolthera van Hövell tot Westerflier + * Copyright (c) 2017 Scott Petrovic + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "VanishingPointAssistant.h" + +#include "kis_debug.h" +#include + +#include +#include +#include + +#include +#include +#include + +#include + +VanishingPointAssistant::VanishingPointAssistant() + : KisPaintingAssistant("vanishing point", i18n("Vanishing Point assistant")) +{ +} + +QPointF VanishingPointAssistant::project(const QPointF& pt, const QPointF& strokeBegin) +{ + //Q_ASSERT(handles().size() == 1 || handles().size() == 5); + //code nicked from the perspective ruler. + qreal dx = pt.x() - strokeBegin.x(); + qreal dy = pt.y() - strokeBegin.y(); + + if (dx * dx + dy * dy < 4.0) { + // allow some movement before snapping + return strokeBegin; + } + + //dbgKrita<canvasWidget()->mapFromGlobal(QCursor::pos()); + m_canvas = canvas; + } + else { + //...of course, you need to have access to a canvas-widget for that.// + mousePos = QCursor::pos();//this'll give an offset// + dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<paintingAssistantsDecoration()->isEditingAssistants() == false && isAssistantComplete()) { + + if (isSnappingActive() && previewVisible == true) { + //don't draw if invalid. + QTransform initialTransform = converter->documentToWidgetTransform(); + QPointF startPoint = initialTransform.map(*handles()[0]); + + QLineF snapLine= QLineF(startPoint, mousePos); + QRect viewport= gc.viewport(); + + KisAlgebra2D::intersectLineRect(snapLine, viewport); + + QRect bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); + + QPainterPath path; + + if (bounds.contains(startPoint.toPoint())){ + path.moveTo(startPoint); + path.lineTo(snapLine.p1()); + } + else + { + path.moveTo(snapLine.p1()); + path.lineTo(snapLine.p2()); + } + + drawPreview(gc, path);//and we draw the preview. + } + } + + + + + // editor specific controls display + if (canvas->paintingAssistantsDecoration()->isEditingAssistants()) { + + // draws a circle around the vanishing point node while editing + QTransform initialTransform = converter->documentToWidgetTransform(); + QPointF p0 = initialTransform.map(*handles()[0]); // main vanishing point + QPointF p1 = initialTransform.map(*sideHandles()[0]); + QPointF p2 = initialTransform.map(*sideHandles()[1]); + QPointF p3 = initialTransform.map(*sideHandles()[2]); + QPointF p4 = initialTransform.map(*sideHandles()[3]); + + + QRectF ellipse = QRectF(QPointF(p0.x() -15, p0.y() -15), QSizeF(30, 30)); + + QPainterPath pathCenter; + pathCenter.addEllipse(ellipse); + drawPath(gc, pathCenter, isSnappingActive()); + + // draw the lines connecting the different nodes + QPen penStyle(m_canvas->paintingAssistantsDecoration()->assistantsColor(), 2.0, Qt::SolidLine); + + if (!isSnappingActive()) { + penStyle.setColor(QColor(m_canvas->paintingAssistantsDecoration()->assistantsColor().red(), + m_canvas->paintingAssistantsDecoration()->assistantsColor().green(), + m_canvas->paintingAssistantsDecoration()->assistantsColor().blue(), + m_canvas->paintingAssistantsDecoration()->assistantsColor().alpha()*.2)); + } + + gc.save(); + gc.setPen(penStyle); + gc.drawLine(p0, p1); + gc.drawLine(p0, p3); + gc.drawLine(p1, p2); + gc.drawLine(p3, p4); + gc.restore(); + } + + + gc.restore(); + + KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); + + + +} + +void VanishingPointAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) +{ + if (!m_canvas || !isAssistantComplete()) { + return; + } + + if (assistantVisible == false || m_canvas->paintingAssistantsDecoration()->isEditingAssistants()) { + return; + } + + QTransform initialTransform = converter->documentToWidgetTransform(); + QPointF p0 = initialTransform.map(*handles()[0]); + + // draws an "X" + QPainterPath path; + path.moveTo(QPointF(p0.x() - 10.0, p0.y() - 10.0)); + path.lineTo(QPointF(p0.x() + 10.0, p0.y() + 10.0)); + + path.moveTo(QPointF(p0.x() - 10.0, p0.y() + 10.0)); + path.lineTo(QPointF(p0.x() + 10.0, p0.y() - 10.0)); + + + drawPath(gc, path, isSnappingActive()); +} + +QPointF VanishingPointAssistant::buttonPosition() const +{ + return (*handles()[0]); +} + +bool VanishingPointAssistant::isAssistantComplete() const +{ + return handles().size() > 0; // only need one point to be ready +} + + +VanishingPointAssistantFactory::VanishingPointAssistantFactory() +{ +} + +VanishingPointAssistantFactory::~VanishingPointAssistantFactory() +{ +} + +QString VanishingPointAssistantFactory::id() const +{ + return "vanishing point"; +} + +QString VanishingPointAssistantFactory::name() const +{ + return i18n("Vanishing Point"); +} + +KisPaintingAssistant* VanishingPointAssistantFactory::createPaintingAssistant() const +{ + return new VanishingPointAssistant; +} diff --git a/plugins/assistants/RulerAssistant/VanishingPointAssistant.h b/plugins/assistants/Assistants/VanishingPointAssistant.h similarity index 95% rename from plugins/assistants/RulerAssistant/VanishingPointAssistant.h rename to plugins/assistants/Assistants/VanishingPointAssistant.h index c1ce729d50..de14c11d2d 100644 --- a/plugins/assistants/RulerAssistant/VanishingPointAssistant.h +++ b/plugins/assistants/Assistants/VanishingPointAssistant.h @@ -1,68 +1,73 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2014 Wolthera van Hövell tot Westerflier + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _VANISHINGPOINT_ASSISTANT_H_ #define _VANISHINGPOINT_ASSISTANT_H_ #include "kis_painting_assistant.h" #include #include #include #include /* Design: *The idea behind the vanishing point ruler is that in a perspective deformed landscape, a set of parallel *lines al share a single vanishing point. *Therefore, a perspective can contain an theoretical infinite of vanishing points. *It's a pity if we only allowed an artist to access 1, 2 or 3 of these at any given time, as other *solutions for perspective tools do. *Hence a vanishing point ruler. * *This ruler is relatively simple compared to the other perspective ruler: *It has only one vanishing point that is required to draw. *However, it does have it's own weaknesses in how to determine onto which of these infinite rulers to snap. *Furthermore, it has four extra handles for adding a perspective ruler to a preexisting perspective. */ //class VanishingPoint; class VanishingPointAssistant : public KisPaintingAssistant { public: VanishingPointAssistant(); QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; //virtual void endStroke(); QPointF buttonPosition() const override; int numHandles() const override { return 1; } + + bool isAssistantComplete() const; + protected: void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override; void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; private: QPointF project(const QPointF& pt, const QPointF& strokeBegin); + KisCanvas2 *m_canvas; }; class VanishingPointAssistantFactory : public KisPaintingAssistantFactory { public: VanishingPointAssistantFactory(); ~VanishingPointAssistantFactory() override; QString id() const override; QString name() const override; KisPaintingAssistant* createPaintingAssistant() const override; }; #endif diff --git a/plugins/assistants/RulerAssistant/ruler_assistant_tool.cc b/plugins/assistants/Assistants/assistant_tool.cc similarity index 82% rename from plugins/assistants/RulerAssistant/ruler_assistant_tool.cc rename to plugins/assistants/Assistants/assistant_tool.cc index 7cc2f6ba34..430421d45b 100644 --- a/plugins/assistants/RulerAssistant/ruler_assistant_tool.cc +++ b/plugins/assistants/Assistants/assistant_tool.cc @@ -1,60 +1,60 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "ruler_assistant_tool.h" -#include "kis_ruler_assistant_tool.h" +#include "assistant_tool.h" +#include "kis_assistant_tool.h" #include #include #include #include #include #include "RulerAssistant.h" #include "EllipseAssistant.h" #include "SplineAssistant.h" #include "PerspectiveAssistant.h" #include "VanishingPointAssistant.h" #include "InfiniteRulerAssistant.h" #include "ParallelRulerAssistant.h" #include "ConcentricEllipseAssistant.h" #include "FisheyePointAssistant.h" //#include "mesh_assistant.h" -K_PLUGIN_FACTORY_WITH_JSON(RulerAssistantToolFactory, "kritarulerassistanttool.json", registerPlugin();) +K_PLUGIN_FACTORY_WITH_JSON(AssistantToolFactory, "kritaassistanttool.json", registerPlugin();) -RulerAssistantToolPlugin::RulerAssistantToolPlugin(QObject *parent, const QVariantList &) +AssistantToolPlugin::AssistantToolPlugin(QObject *parent, const QVariantList &) : QObject(parent) { - KoToolRegistry::instance()->add(new KisRulerAssistantToolFactory()); + KoToolRegistry::instance()->add(new KisAssistantToolFactory()); KisPaintingAssistantFactoryRegistry::instance()->add(new RulerAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new EllipseAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new SplineAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new PerspectiveAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new VanishingPointAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new InfiniteRulerAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new ParallelRulerAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new ConcentricEllipseAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new FisheyePointAssistantFactory); // KisPaintingAssistantFactoryRegistry::instance()->add(new MeshAssistantFactory); } -RulerAssistantToolPlugin::~RulerAssistantToolPlugin() +AssistantToolPlugin::~AssistantToolPlugin() { } -#include "ruler_assistant_tool.moc" +#include "assistant_tool.moc" diff --git a/plugins/assistants/RulerAssistant/ruler_assistant_tool.h b/plugins/assistants/Assistants/assistant_tool.h similarity index 85% rename from plugins/assistants/RulerAssistant/ruler_assistant_tool.h rename to plugins/assistants/Assistants/assistant_tool.h index d3d52949b9..ef76fb0925 100644 --- a/plugins/assistants/RulerAssistant/ruler_assistant_tool.h +++ b/plugins/assistants/Assistants/assistant_tool.h @@ -1,34 +1,34 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _RULERASSISTANTTOOL_H_ #define _RULERASSISTANTTOOL_H_ #include #include -class RulerAssistantToolPlugin : public QObject +class AssistantToolPlugin : public QObject { Q_OBJECT public: - RulerAssistantToolPlugin(QObject *parent, const QVariantList &); - ~RulerAssistantToolPlugin() override; + AssistantToolPlugin(QObject *parent, const QVariantList &); + ~AssistantToolPlugin() override; }; #endif diff --git a/plugins/assistants/RulerAssistant/dark_krita_tool_ruler_assistant.png b/plugins/assistants/Assistants/dark_krita_tool_assistant.png similarity index 100% rename from plugins/assistants/RulerAssistant/dark_krita_tool_ruler_assistant.png rename to plugins/assistants/Assistants/dark_krita_tool_assistant.png diff --git a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc b/plugins/assistants/Assistants/kis_assistant_tool.cc similarity index 60% rename from plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc rename to plugins/assistants/Assistants/kis_assistant_tool.cc index 9300d8a5d3..9be16b9e40 100644 --- a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc +++ b/plugins/assistants/Assistants/kis_assistant_tool.cc @@ -1,913 +1,794 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song + * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include +#include #include #include #include #include #include #include #include #include +#include #include #include -#include #include #include #include +#include #include #include #include #include #include - +#include #include #include - #include "kis_global.h" + #include -KisRulerAssistantTool::KisRulerAssistantTool(KoCanvasBase * canvas) +KisAssistantTool::KisAssistantTool(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast(canvas)), - m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0), m_handleSize(32), m_handleHalfSize(16) + m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0) { Q_ASSERT(m_canvas); - setObjectName("tool_rulerassistanttool"); -} - -KisRulerAssistantTool::~KisRulerAssistantTool() -{ + setObjectName("tool_assistanttool"); } -QPointF adjustPointF(const QPointF& _pt, const QRectF& _rc) +KisAssistantTool::~KisAssistantTool() { - return QPointF(qBound(_rc.left(), _pt.x(), _rc.right()), qBound(_rc.top(), _pt.y(), _rc.bottom())); } -void KisRulerAssistantTool::activate(ToolActivation toolActivation, const QSet &shapes) +void KisAssistantTool::activate(ToolActivation toolActivation, const QSet &shapes) { - // Add code here to initialize your tool when it got activated KisTool::activate(toolActivation, shapes); + m_canvas->paintingAssistantsDecoration()->activateAssistantsEditor(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); - m_canvas->paintingAssistantsDecoration()->setVisible(true); - m_canvas->updateCanvas(); + m_handleDrag = 0; m_internalMode = MODE_CREATION; m_assistantHelperYOffset = 10; -} + m_canvas->paintingAssistantsDecoration()->setHandleSize(17); + m_handleSize = 17; -void KisRulerAssistantTool::deactivate() -{ - // Add code here to initialize your tool when it got deactivated m_canvas->updateCanvas(); - KisTool::deactivate(); -} - -bool KisRulerAssistantTool::mouseNear(const QPointF& mousep, const QPointF& point) -{ - QRectF handlerect(point-QPointF(m_handleHalfSize,m_handleHalfSize), QSizeF(m_handleSize, m_handleSize)); - return handlerect.contains(mousep); -} - -KisPaintingAssistantHandleSP KisRulerAssistantTool::nodeNearPoint(KisPaintingAssistantSP grid, QPointF point) -{ - if (mouseNear(point, pixelToView(*grid->topLeft()))) { - return grid->topLeft(); - } else if (mouseNear(point, pixelToView(*grid->topRight()))) { - return grid->topRight(); - } else if (mouseNear(point, pixelToView(*grid->bottomLeft()))) { - return grid->bottomLeft(); - } else if (mouseNear(point, pixelToView(*grid->bottomRight()))) { - return grid->bottomRight(); - } - return 0; } -inline double norm2(const QPointF& p) +void KisAssistantTool::deactivate() { - return p.x() * p.x() + p.y() * p.y(); + m_canvas->paintingAssistantsDecoration()->deactivateAssistantsEditor(); + m_canvas->updateCanvas(); + KisTool::deactivate(); } -void KisRulerAssistantTool::beginPrimaryAction(KoPointerEvent *event) +void KisAssistantTool::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); bool newAssistantAllowed = true; + KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration(); + if (m_newAssistant) { m_internalMode = MODE_CREATION; - *m_newAssistant->handles().back() = snapToGuide(event, QPointF(), false); + *m_newAssistant->handles().back() = canvasDecoration->snapToGuide(event, QPointF(), false); if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) { addAssistant(); } else { - m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); } m_canvas->updateCanvas(); return; } m_handleDrag = 0; double minDist = 81.0; - QPointF mousePos = m_canvas->viewConverter()->documentToView(snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point); + + QPointF mousePos = m_canvas->viewConverter()->documentToView(canvasDecoration->snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point); + + Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { + + // find out which handle on all assistants is closes to the mouse position Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { - double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); + double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleDrag = handle; } } + + if(m_handleDrag && assistant->id() == "perspective") { // Look for the handle which was pressed + if (m_handleDrag == assistant->topLeft()) { - double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); + double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_internalMode = MODE_DRAGGING_NODE; } else if (m_handleDrag == assistant->topRight()) { - double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); + double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); } else if (m_handleDrag == assistant->bottomLeft()) { - double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); + double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); } else if (m_handleDrag == assistant->bottomRight()) { - double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); + double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); } else if (m_handleDrag == assistant->leftMiddle()) { m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5); m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); - m_newAssistant->addHandle(assistant->topLeft()); - m_newAssistant->addHandle(m_selectedNode1); - m_newAssistant->addHandle(m_selectedNode2); - m_newAssistant->addHandle(assistant->bottomLeft()); + m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL ); + m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); + m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); + m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->rightMiddle()) { m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); - m_newAssistant->addHandle(assistant->topRight()); - m_newAssistant->addHandle(m_selectedNode1); - m_newAssistant->addHandle(m_selectedNode2); - m_newAssistant->addHandle(assistant->bottomRight()); + m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL); + m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); + m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); + m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->topMiddle()) { m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5, (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); - m_newAssistant->addHandle(m_selectedNode1); - m_newAssistant->addHandle(m_selectedNode2); - m_newAssistant->addHandle(assistant->topRight()); - m_newAssistant->addHandle(assistant->topLeft()); + m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); + m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); + m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL); + m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->bottomMiddle()) { m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); - m_newAssistant->addHandle(assistant->bottomLeft()); - m_newAssistant->addHandle(assistant->bottomRight()); - m_newAssistant->addHandle(m_selectedNode2); - m_newAssistant->addHandle(m_selectedNode1); + m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL); + m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL); + m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); + m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" || assistant->id() == "parallel ruler" || assistant->id() == "infinite ruler" || assistant->id() == "spline")){ if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if(assistant->handles().size()==4){ if (m_handleDrag == assistant->handles()[2]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[3]) { m_dragStart = *assistant->handles()[1]; } } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" || assistant->id() == "concentric ellipse" || assistant->id() == "fisheye-point")){ m_snapIsRadial = false; if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[2]) { m_dragStart = assistant->buttonPosition(); m_radius = QLineF(m_dragStart, *assistant->handles()[0]); m_snapIsRadial = true; } } else { m_dragStart = assistant->buttonPosition(); m_snapIsRadial = false; } } if (m_handleDrag) { // TODO: Shift-press should now be handled using the alternate actions // if (event->modifiers() & Qt::ShiftModifier) { // m_handleDrag->uncache(); // m_handleDrag = m_handleDrag->split()[0]; // m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles(); // } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_assistantDrag.clear(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { - // This code contains the click event behavior. The actual display of the icons are done at the bottom - // of the paint even. Make sure the rectangles positions are the same between the two. - - // TODO: These 6 lines are duplicated below in the paint layer. It shouldn't be done like this. + // This code contains the click event behavior. QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition()); - QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7)); - QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7)); - QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset)); + AssistantEditorData editorShared; // shared position data between assistant tool and decoration + QPointF iconMovePosition(actionsPosition + editorShared.moveIconPosition); + QPointF iconSnapPosition(actionsPosition + editorShared.snapIconPosition); + QPointF iconDeletePosition(actionsPosition + editorShared.deleteIconPosition); - QRectF deleteRect(iconDeletePosition, QSizeF(16, 16)); - QRectF visibleRect(iconSnapPosition, QSizeF(16, 16)); - QRectF moveRect(iconMovePosition, QSizeF(32, 32)); + QRectF deleteRect(iconDeletePosition, QSizeF(editorShared.deleteIconSize, editorShared.deleteIconSize)); + QRectF visibleRect(iconSnapPosition, QSizeF(editorShared.snapIconSize, editorShared.snapIconSize)); + QRectF moveRect(iconMovePosition, QSizeF(editorShared.moveIconSize, editorShared.moveIconSize)); if (moveRect.contains(mousePos)) { m_assistantDrag = assistant; m_cursorStart = event->point; m_currentAdjustment = QPointF(); m_internalMode = MODE_EDITING; return; } + if (deleteRect.contains(mousePos)) { removeAssistant(assistant); if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) { m_internalMode = MODE_CREATION; } else m_internalMode = MODE_EDITING; m_canvas->updateCanvas(); return; } if (visibleRect.contains(mousePos)) { newAssistantAllowed = false; - if (assistant->snapping()==true){ - - snappingOff(assistant); - outlineOff(assistant); - } - else{ - snappingOn(assistant); - outlineOn(assistant); - } + assistant->setSnappingActive(!assistant->isSnappingActive()); // toggle assistant->uncache();//this updates the chache of the assistant, very important. } } if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visiblity// - QString key = m_options.comboBox->model()->index( m_options.comboBox->currentIndex(), 0 ).data(Qt::UserRole).toString(); + QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString(); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant()); m_internalMode = MODE_CREATION; - m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); - if (m_newAssistant->numHandles() <= 1) { - if (key == "vanishing point"){ - m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-70,0))); - m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-140,0))); - m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(70,0))); - m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(140,0))); - } + m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); + if (m_newAssistant->numHandles() <= 1) { addAssistant(); } else { - m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); } } m_canvas->updateCanvas(); } -void KisRulerAssistantTool::continuePrimaryAction(KoPointerEvent *event) +void KisAssistantTool::continuePrimaryAction(KoPointerEvent *event) { + KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration(); + if (m_handleDrag) { *m_handleDrag = event->point; //ported from the gradient tool... we need to think about this more in the future. if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) { QLineF dragRadius = QLineF(m_dragStart, event->point); dragRadius.setLength(m_radius.length()); *m_handleDrag = dragRadius.p2(); } else if (event->modifiers() == Qt::ShiftModifier ) { QPointF move = snapToClosestAxis(event->point - m_dragStart); *m_handleDrag = m_dragStart + move; } else { - *m_handleDrag = snapToGuide(event, QPointF(), false); + *m_handleDrag = canvasDecoration->snapToGuide(event, QPointF(), false); } m_handleDrag->uncache(); m_handleCombine = 0; if (!(event->modifiers() & Qt::ShiftModifier)) { double minDist = 49.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { - if (handle == m_handleDrag) continue; - double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); + if (handle == m_handleDrag) + continue; + + + double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleCombine = handle; } } } m_canvas->updateCanvas(); } else if (m_assistantDrag) { - QPointF newAdjustment = snapToGuide(event, QPointF(), false) - m_cursorStart; + QPointF newAdjustment = canvasDecoration->snapToGuide(event, QPointF(), false) - m_cursorStart; if (event->modifiers() == Qt::ShiftModifier ) { newAdjustment = snapToClosestAxis(newAdjustment); } Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) { *handle += (newAdjustment - m_currentAdjustment); } if (m_assistantDrag->id()== "vanishing point"){ Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) { *handle += (newAdjustment - m_currentAdjustment); } } m_currentAdjustment = newAdjustment; m_canvas->updateCanvas(); } else { event->ignore(); } bool wasHiglightedNode = m_higlightedNode != 0; QPointF mousep = m_canvas->viewConverter()->documentToView(event->point); QList pAssistant= m_canvas->paintingAssistantsDecoration()->assistants(); - Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) { + + Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) { if(assistant->id() == "perspective") { - if ((m_higlightedNode = nodeNearPoint(assistant, mousep))) { + if ((m_higlightedNode = assistant->closestCornerHandleFromPoint(mousep))) { if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) { m_higlightedNode = 0; } else { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas break; } } } //this following bit sets the translations for the vanishing-point handles. if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) { //for inner handles, the outer handle gets translated. - if (m_handleDrag == assistant->sideHandles()[0]){ - QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); - - qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); - if (length<2.0){length=2.0;} - length +=perspectiveline.length(); - perspectiveline.setLength(length); - *assistant->sideHandles()[1] = perspectiveline.p2(); - } + if (m_handleDrag == assistant->sideHandles()[0]) { + QLineF perspectiveline = QLineF(*assistant->handles()[0], + *assistant->sideHandles()[0]); + + qreal length = QLineF(*assistant->sideHandles()[0], + *assistant->sideHandles()[1]).length(); + + if (length < 2.0){ + length = 2.0; + } + + length += perspectiveline.length(); + perspectiveline.setLength(length); + *assistant->sideHandles()[1] = perspectiveline.p2(); + } else if (m_handleDrag == assistant->sideHandles()[2]){ - QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); + QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); + qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); - qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); - if (length<2.0){length=2.0;} - length +=perspectiveline.length(); - perspectiveline.setLength(length); - *assistant->sideHandles()[3] = perspectiveline.p2(); - } - //for outer handles, only the vanishing point is translated, but only if there's an intersection. + if (length<2.0){ + length=2.0; + } + + length += perspectiveline.length(); + perspectiveline.setLength(length); + *assistant->sideHandles()[3] = perspectiveline.p2(); + } // for outer handles, only the vanishing point is translated, but only if there's an intersection. else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){ - QPointF vanishingpoint(0,0); - QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]); - QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]); - if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){ - *assistant->handles()[0] = vanishingpoint;} - }//and for the vanishing point itself, only the outer handles get translated. + QPointF vanishingpoint(0,0); + QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]); + QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]); + + if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){ + *assistant->handles()[0] = vanishingpoint; + } + }// and for the vanishing point itself, only the outer handles get translated. else if (m_handleDrag == assistant->handles()[0]){ - QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); - QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); - qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); - qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); - if (length<2.0){length=2.0;} - if (length2<2.0){length2=2.0;} - length +=perspectiveline.length(); - length2 +=perspectiveline2.length(); - perspectiveline.setLength(length); - perspectiveline2.setLength(length2); - *assistant->sideHandles()[1] = perspectiveline.p2(); - *assistant->sideHandles()[3] = perspectiveline2.p2(); + QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); + QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); + qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); + qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); + + if (length < 2.0) { + length = 2.0; + } + + if (length2 < 2.0) { + length2=2.0; + } + + length += perspectiveline.length(); + length2 += perspectiveline2.length(); + perspectiveline.setLength(length); + perspectiveline2.setLength(length2); + *assistant->sideHandles()[1] = perspectiveline.p2(); + *assistant->sideHandles()[3] = perspectiveline2.p2(); } } } if (wasHiglightedNode && !m_higlightedNode) { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } } -void KisRulerAssistantTool::endPrimaryAction(KoPointerEvent *event) +void KisAssistantTool::endPrimaryAction(KoPointerEvent *event) { setMode(KisTool::HOVER_MODE); if (m_handleDrag) { if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) { m_handleCombine->mergeWith(m_handleDrag); m_handleCombine->uncache(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } m_handleDrag = m_handleCombine = 0; - m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas + } else if (m_assistantDrag) { m_assistantDrag.clear(); - m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { addAssistant(); m_internalMode = MODE_CREATION; - m_canvas->updateCanvas(); } else { event->ignore(); } + + m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } -void KisRulerAssistantTool::addAssistant() +void KisAssistantTool::addAssistant() { m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); KisAbstractPerspectiveGrid* grid = dynamic_cast(m_newAssistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } m_newAssistant.clear(); } - -void KisRulerAssistantTool::removeAssistant(KisPaintingAssistantSP assistant) +void KisAssistantTool::removeAssistant(KisPaintingAssistantSP assistant) { KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->removePerspectiveGrid(grid); } m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } -void KisRulerAssistantTool::snappingOn(KisPaintingAssistantSP assistant) -{ - assistant->setSnapping(true); -} - -void KisRulerAssistantTool::snappingOff(KisPaintingAssistantSP assistant) -{ - assistant->setSnapping(false); -} - -void KisRulerAssistantTool::outlineOn(KisPaintingAssistantSP assistant) -{ - assistant->setOutline(true); -} - -void KisRulerAssistantTool::outlineOff(KisPaintingAssistantSP assistant) -{ - assistant->setOutline(false); -} - -#include - -QPointF KisRulerAssistantTool::snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers) -{ - if (!m_canvas->currentImage()) - return e->point; - - KoSnapGuide *snapGuide = m_canvas->snapGuide(); - QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); - - //return m_canvas->currentImage()->documentToPixel(pos); - return pos; -} - -QPointF KisRulerAssistantTool::snapToGuide(const QPointF& pt, const QPointF &offset) -{ - if (!m_canvas) - return pt; - - KoSnapGuide *snapGuide = m_canvas->snapGuide(); - QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); - - return pos; -} - -void KisRulerAssistantTool::mouseMoveEvent(KoPointerEvent *event) +void KisAssistantTool::mouseMoveEvent(KoPointerEvent *event) { if (m_newAssistant && m_internalMode == MODE_CREATION) { *m_newAssistant->handles().back() = event->point; - m_canvas->updateCanvas(); + } else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { QPointF translate = event->point - m_dragEnd;; m_dragEnd = event->point; - m_selectedNode1.data()->operator =(QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate); + m_selectedNode1.data()->operator = (QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate); m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate); - m_canvas->updateCanvas(); } + + m_canvas->updateCanvas(); } -void KisRulerAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter) +void KisAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter) { + QRectF canvasSize = QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())); + QColor assistantColor = m_canvas->paintingAssistantsDecoration()->assistantsColor(); + QBrush fillAssistantColorBrush; + fillAssistantColorBrush.setColor(m_canvas->paintingAssistantsDecoration()->assistantsColor()); - QPixmap iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(16, 16); - QPixmap iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(16, 16); - QPixmap iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(16, 16); - QPixmap iconMove = KisIconUtils::loadIcon("transform-move").pixmap(32, 32); - QColor handlesColor(0, 0, 0, 125); - + // show special display while a new assistant is in the process of being created if (m_newAssistant) { - m_newAssistant->drawAssistant(_gc, QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())), m_canvas->coordinatesConverter(), false,m_canvas, true, false); + + m_newAssistant->setAssistantColor(assistantColor); + m_newAssistant->drawAssistant(_gc, canvasSize, m_canvas->coordinatesConverter(), false,m_canvas, true, false); + Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) { QPainterPath path; - path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12))); - KisPaintingAssistant::drawPath(_gc, path); + path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize))); + + _gc.save(); + _gc.setPen(Qt::NoPen); + _gc.setBrush(assistantColor); + _gc.drawPath(path); + _gc.restore(); } } - // TODO: too many Q_FOREACH loops going through all assistants. Condense this to one to be a little more performant - // Draw corner and middle perspective nodes Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { + + Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { - QRectF ellipse(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12)); + QRectF ellipse(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), + QSizeF(m_handleSize, m_handleSize)); - // render handles when they are being dragged and moved + // render handles differently if it is the one being dragged. if (handle == m_handleDrag || handle == m_handleCombine) { + QPen stroke(QColor(assistantColor.red(), assistantColor.green(), assistantColor.blue(), 80), 4); _gc.save(); - _gc.setPen(Qt::transparent); - _gc.setBrush(handlesColor); + _gc.setPen(stroke); + _gc.setBrush(Qt::NoBrush); _gc.drawEllipse(ellipse); _gc.restore(); } - if ( assistant->id() =="vanishing point") { - - if (assistant->handles().at(0) == handle ) { // vanishing point handle - ellipse = QRectF(_converter.documentToView(*handle) - QPointF(10, 10), QSizeF(20, 20)); - // TODO: change this to be smaller, but fill in with a color - } - - //TODO: render outside handles a little bigger than rotation anchor handles - } - - QPainterPath path; - path.addEllipse(ellipse); - KisPaintingAssistant::drawPath(_gc, path); - - } - } - - - - Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { - // Draw middle perspective handles - if(assistant->id()=="perspective") { - assistant->findHandleLocation(); - QPointF topMiddle, bottomMiddle, rightMiddle, leftMiddle; - topMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->topRight()))*0.5; - bottomMiddle = (_converter.documentToView(*assistant->bottomLeft()) + _converter.documentToView(*assistant->bottomRight()))*0.5; - rightMiddle = (_converter.documentToView(*assistant->topRight()) + _converter.documentToView(*assistant->bottomRight()))*0.5; - leftMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->bottomLeft()))*0.5; - QPainterPath path; - path.addEllipse(QRectF(leftMiddle-QPointF(6,6),QSizeF(12,12))); - path.addEllipse(QRectF(topMiddle-QPointF(6,6),QSizeF(12,12))); - path.addEllipse(QRectF(rightMiddle-QPointF(6,6),QSizeF(12,12))); - path.addEllipse(QRectF(bottomMiddle-QPointF(6,6),QSizeF(12,12))); - KisPaintingAssistant::drawPath(_gc, path); - } - if(assistant->id()=="vanishing point") { - if (assistant->sideHandles().size() == 4) { - // Draw the line - QPointF p0 = _converter.documentToView(*assistant->handles()[0]); - QPointF p1 = _converter.documentToView(*assistant->sideHandles()[0]); - QPointF p2 = _converter.documentToView(*assistant->sideHandles()[1]); - QPointF p3 = _converter.documentToView(*assistant->sideHandles()[2]); - QPointF p4 = _converter.documentToView(*assistant->sideHandles()[3]); - - _gc.setPen(QColor(0, 0, 0, 75)); - // Draw control lines - QPen penStyle(QColor(120, 120, 120, 60), 2.0, Qt::DashDotDotLine); - _gc.setPen(penStyle); - _gc.drawLine(p0, p1); - _gc.drawLine(p0, p3); - _gc.drawLine(p1, p2); - _gc.drawLine(p3, p4); - } } } - - // Draw the assistant widget - Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { - - - // We are going to put all of the assistant actions below the bounds of the assistant - // so they are out of the way - // assistant->buttonPosition() gets the center X/Y position point - QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition()); - - QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7)); - QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7)); - QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset )); - - - - // Background container for helpers - QBrush backgroundColor = m_canvas->viewManager()->mainWindow()->palette().window(); - QPointF actionsBGRectangle(actionsPosition + QPointF(25, m_assistantHelperYOffset)); - - _gc.setRenderHint(QPainter::Antialiasing); - - QPainterPath bgPath; - bgPath.addRoundedRect(QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), 80, 30), 6, 6); - QPen stroke(QColor(60, 60, 60, 80), 2); - _gc.setPen(stroke); - _gc.fillPath(bgPath, backgroundColor); - _gc.drawPath(bgPath); - - - QPainterPath movePath; // render circle behind by move helper - _gc.setPen(stroke); - movePath.addEllipse(iconMovePosition.x()-5, iconMovePosition.y()-5, 40, 40);// background behind icon - _gc.fillPath(movePath, backgroundColor); - _gc.drawPath(movePath); - - // Preview/Snap Tool helper - _gc.drawPixmap(iconDeletePosition, iconDelete); - if (assistant->snapping()==true) { - _gc.drawPixmap(iconSnapPosition, iconSnapOn); - } - else - { - _gc.drawPixmap(iconSnapPosition, iconSnapOff); - } - - - // Move Assistant Tool helper - _gc.drawPixmap(iconMovePosition, iconMove); - - - } } -void KisRulerAssistantTool::removeAllAssistants() +void KisAssistantTool::removeAllAssistants() { m_canvas->viewManager()->resourceProvider()->clearPerspectiveGrids(); m_canvas->paintingAssistantsDecoration()->removeAll(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } -void KisRulerAssistantTool::loadAssistants() +void KisAssistantTool::loadAssistants() { KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant"); dialog.setCaption(i18n("Select an Assistant")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; QFile file(filename); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); QXmlStreamReader xml(data); QMap handleMap; KisPaintingAssistantSP assistant; bool errors = false; while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "handle") { if (assistant && !xml.attributes().value("ref").isEmpty()) { KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt()); if (handle) { - assistant->addHandle(handle); + assistant->addHandle(handle, HandleType::NORMAL); } else { errors = true; } } else { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { int id = strId.toInt(); double x = strX.toDouble(), y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } else { errors = true; } } else { errors = true; } } } else if (xml.name() == "assistant") { const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString()); if (factory) { if (assistant) { errors = true; assistant.clear(); } assistant = toQShared(factory->createPaintingAssistant()); } else { errors = true; } } break; case QXmlStreamReader::EndElement: if (xml.name() == "assistant") { if (assistant) { if (assistant->handles().size() == assistant->numHandles()) { if (assistant->id() == "vanishing point"){ //ideally we'd save and load side-handles as well, but this is all I've got// QPointF pos = *assistant->handles()[0]; - assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0))); - assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0))); - assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0))); - assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0))); + assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0)), HandleType::SIDE); + assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0)), HandleType::SIDE); + assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0)), HandleType::SIDE); + assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0)), HandleType::SIDE); } m_canvas->paintingAssistantsDecoration()->addAssistant(assistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } } else { errors = true; } assistant.clear(); } } break; default: break; } } if (assistant) { errors = true; assistant.clear(); } if (xml.hasError()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString()); } if (errors) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded.")); } m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } -void KisRulerAssistantTool::saveAssistants() +void KisAssistantTool::saveAssistants() { if (m_handles.isEmpty()) return; QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("paintingassistant"); xml.writeStartElement("handles"); QMap handleMap; Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { int id = handleMap.size(); handleMap.insert(handle, id); xml.writeStartElement("handle"); //xml.writeAttribute("type", handle->handleType()); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeStartElement("assistants"); Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { xml.writeStartElement("assistant"); xml.writeAttribute("type", assistant->id()); xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { xml.writeStartElement("handle"); xml.writeAttribute("ref", QString::number(handleMap.value(handle))); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant"); dialog.setCaption(i18n("Save Assistant")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QFile file(filename); file.open(QIODevice::WriteOnly); file.write(data); } - - -QWidget *KisRulerAssistantTool::createOptionWidget() +QWidget *KisAssistantTool::createOptionWidget() { if (!m_optionsWidget) { m_optionsWidget = new QWidget; m_options.setupUi(m_optionsWidget); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); - m_options.loadButton->setIcon(KisIconUtils::loadIcon("document-open")); - m_options.saveButton->setIcon(KisIconUtils::loadIcon("document-save")); - m_options.deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete")); + m_options.loadAssistantButton->setIcon(KisIconUtils::loadIcon("document-open")); + m_options.saveAssistantButton->setIcon(KisIconUtils::loadIcon("document-save")); + m_options.deleteAllAssistantsButton->setIcon(KisIconUtils::loadIcon("edit-delete")); QList assistants; Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) { QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name(); assistants << KoID(key, name); } std::sort(assistants.begin(), assistants.end(), KoID::compareNames); Q_FOREACH(const KoID &id, assistants) { - m_options.comboBox->addItem(id.name(), id.id()); + m_options.availableAssistantsComboBox->addItem(id.name(), id.id()); } - connect(m_options.saveButton, SIGNAL(clicked()), SLOT(saveAssistants())); - connect(m_options.loadButton, SIGNAL(clicked()), SLOT(loadAssistants())); - connect(m_options.deleteButton, SIGNAL(clicked()), SLOT(removeAllAssistants())); + connect(m_options.saveAssistantButton, SIGNAL(clicked()), SLOT(saveAssistants())); + connect(m_options.loadAssistantButton, SIGNAL(clicked()), SLOT(loadAssistants())); + connect(m_options.deleteAllAssistantsButton, SIGNAL(clicked()), SLOT(removeAllAssistants())); + + connect(m_options.assistantsColor, SIGNAL(changed(const QColor&)), SLOT(slotAssistantsColorChanged(const QColor&))); + connect(m_options.assistantsOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotAssistantOpacityChanged())); + + + m_options.assistantsColor->setColor(QColor(176, 176, 176, 255)); // grey default for all assistants + m_options.assistantsOpacitySlider->setValue(100); // 100% + m_options.assistantsOpacitySlider->setSuffix(" %"); + + m_assistantsOpacity = m_options.assistantsOpacitySlider->value()*0.01; + + QColor newColor = m_options.assistantsColor->color(); + newColor.setAlpha(m_assistantsOpacity*255); + m_canvas->paintingAssistantsDecoration()->setAssistantsColor(newColor); } return m_optionsWidget; } +void KisAssistantTool::slotAssistantsColorChanged(const QColor& setColor) +{ + // color and alpha are stored separately, so we need to merge the values before sending it on + QColor newColor = setColor; + newColor.setAlpha(m_assistantsOpacity * 255); + m_canvas->paintingAssistantsDecoration()->setAssistantsColor(newColor); + m_canvas->canvasWidget()->update(); +} + +void KisAssistantTool::slotAssistantOpacityChanged() +{ + QColor newColor = m_canvas->paintingAssistantsDecoration()->assistantsColor(); + m_assistantsOpacity = m_options.assistantsOpacitySlider->value()*0.01; + newColor.setAlpha(m_assistantsOpacity*255); + m_canvas->paintingAssistantsDecoration()->setAssistantsColor(newColor); + m_canvas->canvasWidget()->update(); +} diff --git a/plugins/assistants/Assistants/kis_assistant_tool.h b/plugins/assistants/Assistants/kis_assistant_tool.h new file mode 100644 index 0000000000..ec2e0abab4 --- /dev/null +++ b/plugins/assistants/Assistants/kis_assistant_tool.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2008 Cyrille Berger + * Copyright (c) 2017 Scott Petrovic + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _KIS_ASSISTANT_TOOL_H_ +#define _KIS_ASSISTANT_TOOL_H_ + +#include + +#include +#include + +#include +#include "kis_painting_assistant.h" +#include +#include + +#include "ui_AssistantsToolOptions.h" + +/* The assistant tool allows artists to create special guides on the canvas + * to help them with things like perspective and parallel lines + * This tool has its own canvas decoration on it that only appears when the tool + * is active. This decoration allows people to edit assistant points as well as delete assistants + * Many of the operations here are forwarded on to another class (kis_painting_assistant_decoration) + * that stores the assistant information as well as the decoration information with lines + * + * Drawing in two separate classes creates an issue where the editor controls in this class + * are covered by the kis_painting_assistant_decoration class. In the future, we probably need to + * do all the drawing in one class so we have better control of what is in front + */ +class KisAssistantTool : public KisTool +{ + Q_OBJECT + enum PerspectiveAssistantEditionMode { + MODE_CREATION, // This is the mode when there is not yet a perspective grid + MODE_EDITING, // This is the mode when the grid has been created, and we are waiting for the user to click on a control box + MODE_DRAGGING_NODE, // In this mode one node is translated + MODE_DRAGGING_TRANSLATING_TWONODES // This mode is used when creating a new sub perspective grid + }; +public: + KisAssistantTool(KoCanvasBase * canvas); + ~KisAssistantTool() override; + + virtual quint32 priority() { + return 3; + } + + + /* this is a very big function that has to figure out if we are adding a new assistant, + * or editing an existing one when we click on the canvas. There is also a lot of logic + * in here that is specific to certain assistants and how they should be handled. + * The editor widget is not a UI file, so the move, delete, preview areas have manual + * hitbox regions specified to know if a click is doing any of those actions. + */ + void beginPrimaryAction(KoPointerEvent *event) override; + + + void continuePrimaryAction(KoPointerEvent *event) override; + void endPrimaryAction(KoPointerEvent *event) override; + void mouseMoveEvent(KoPointerEvent *event) override; + + QWidget *createOptionWidget() override; + +private: + // adds and removes assistant. + // this is event is forwarded to the kis_painting_decoration class + // perspective grids seem to be managed in two places with these calls + void addAssistant(); + void removeAssistant(KisPaintingAssistantSP assistant); + +public Q_SLOTS: + void activate(ToolActivation toolActivation, const QSet &shapes) override; + void deactivate() override; + +private Q_SLOTS: + void removeAllAssistants(); + void saveAssistants(); + void loadAssistants(); + + /// send the color and opacity information from the UI to the kis_painting_decoration + /// which manages the assistants + void slotAssistantsColorChanged(const QColor&); + void slotAssistantOpacityChanged(); + +protected: + /// Draws the editor widget controls with move, activate, and delete + /// This also creates a lot of assistant specific stuff for vanishing points and perspective grids + /// Whatever is painted here will be underneath the content painted in the kis_painting_assistant_decoration + /// The kis_painting_assistant_decoration paints the final assistant, so this is more of just editor controls + void paint(QPainter& gc, const KoViewConverter &converter) override; + +protected: + /// this class manipulates the kis_painting_assistant_decorations a lot, so this class is a helper + /// to get a reference to it and call "updateCanvas" which refreshes the display + QPointer m_canvas; + + /// the handles are retrieved from the kis_painting_decoration originally + /// They are used here to generate and manipulate editor handles with the tool's primary action + QList m_handles; + QList m_sideHandles; + KisPaintingAssistantHandleSP m_handleDrag; + KisPaintingAssistantHandleSP m_handleCombine; + KisPaintingAssistantSP m_assistantDrag; + + /// Used while a new assistant is being created. Most assistants need multiple points to exist + /// so this helps manage the visual state while this creation process is going on + KisPaintingAssistantSP m_newAssistant; + + QPointF m_cursorStart; + QPointF m_currentAdjustment; + Ui::AssistantsToolOptions m_options; + QWidget* m_optionsWidget; + QPointF m_dragStart; + QLineF m_radius; + bool m_snapIsRadial; + QPointF m_dragEnd; + int m_handleSize; // how large the editor handles will appear + + +private: + void drawEditorWidget(KisPaintingAssistantSP assistant, QPainter& _gc); + + PerspectiveAssistantEditionMode m_internalMode; + KisPaintingAssistantHandleSP m_selectedNode1, m_selectedNode2, m_higlightedNode; + int m_assistantHelperYOffset; // used by the assistant editor icons for placement on the canvas. + + // what color and opacity will the assistants have + // all assistant types will share this setting + QColor m_assistantColor; + float m_assistantsOpacity; + +}; + + +class KisAssistantToolFactory : public KoToolFactoryBase +{ +public: + KisAssistantToolFactory() + : KoToolFactoryBase("KisAssistantTool") { + setToolTip(i18n("Assistant Tool")); + setSection(TOOL_TYPE_VIEW); + setIconName(koIconNameCStr("krita_tool_assistant")); + setPriority(0); + setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); + } + + + ~KisAssistantToolFactory() override {} + + KoToolBase * createTool(KoCanvasBase * canvas) override { + return new KisAssistantTool(canvas); + } + +}; + + +#endif + diff --git a/plugins/assistants/RulerAssistant/light_krita_tool_ruler_assistant.png b/plugins/assistants/Assistants/krita_tool_assistant.png similarity index 100% copy from plugins/assistants/RulerAssistant/light_krita_tool_ruler_assistant.png copy to plugins/assistants/Assistants/krita_tool_assistant.png diff --git a/plugins/assistants/RulerAssistant/kritarulerassistanttool.json b/plugins/assistants/Assistants/kritaassistanttool.json similarity index 58% rename from plugins/assistants/RulerAssistant/kritarulerassistanttool.json rename to plugins/assistants/Assistants/kritaassistanttool.json index b069ce9f4e..bde785dbc9 100644 --- a/plugins/assistants/RulerAssistant/kritarulerassistanttool.json +++ b/plugins/assistants/Assistants/kritaassistanttool.json @@ -1,9 +1,9 @@ { - "Id": "RulerAssistantTool", + "Id": "AssistantTool", "Type": "Service", - "X-KDE-Library": "kritarulerassistanttool", + "X-KDE-Library": "kritaassistanttool", "X-KDE-ServiceTypes": [ "Krita/Tool" ], "X-Krita-Version": "28" } diff --git a/plugins/assistants/RulerAssistant/light_krita_tool_ruler_assistant.png b/plugins/assistants/Assistants/light_krita_tool_assistant.png similarity index 100% rename from plugins/assistants/RulerAssistant/light_krita_tool_ruler_assistant.png rename to plugins/assistants/Assistants/light_krita_tool_assistant.png diff --git a/plugins/assistants/CMakeLists.txt b/plugins/assistants/CMakeLists.txt index aa0146b3a8..a41394c2e2 100644 --- a/plugins/assistants/CMakeLists.txt +++ b/plugins/assistants/CMakeLists.txt @@ -1,5 +1,5 @@ -add_subdirectory( RulerAssistant ) +add_subdirectory( Assistants ) install( FILES - RulerAssistant/KisRulerAssistantTool.action + Assistants/KisAssistantTool.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) diff --git a/plugins/assistants/RulerAssistant/AssistantsToolOptions.ui b/plugins/assistants/RulerAssistant/AssistantsToolOptions.ui deleted file mode 100644 index 061da13ce1..0000000000 --- a/plugins/assistants/RulerAssistant/AssistantsToolOptions.ui +++ /dev/null @@ -1,68 +0,0 @@ - - - AssistantsToolOptions - - - - 0 - 0 - 246 - 105 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - Delete all - - - - - - - Save... - - - - - - - Open... - - - - - - - - diff --git a/plugins/assistants/RulerAssistant/CMakeLists.txt b/plugins/assistants/RulerAssistant/CMakeLists.txt deleted file mode 100644 index 9d7c35b1aa..0000000000 --- a/plugins/assistants/RulerAssistant/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -set(kritarulerassistanttool_SOURCES - RulerAssistant.cc - EllipseAssistant.cc - Ellipse.cc - SplineAssistant.cc - PerspectiveAssistant.cc - VanishingPointAssistant.cc - InfiniteRulerAssistant.cc - ParallelRulerAssistant.cc - ConcentricEllipseAssistant.cc - ruler_assistant_tool.cc - kis_ruler_assistant_tool.cc - FisheyePointAssistant.cc - ) - -ki18n_wrap_ui(kritarulerassistanttool_SOURCES AssistantsToolOptions.ui ) - -add_library(kritarulerassistanttool MODULE ${kritarulerassistanttool_SOURCES}) - -target_link_libraries(kritarulerassistanttool kritaui kritaflake ) - -install(TARGETS kritarulerassistanttool DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) - -install( FILES krita_tool_ruler_assistant.png dark_krita_tool_ruler_assistant.png light_krita_tool_ruler_assistant.png DESTINATION ${DATA_INSTALL_DIR}/krita/pics) diff --git a/plugins/assistants/RulerAssistant/VanishingPointAssistant.cc b/plugins/assistants/RulerAssistant/VanishingPointAssistant.cc deleted file mode 100644 index a277b80610..0000000000 --- a/plugins/assistants/RulerAssistant/VanishingPointAssistant.cc +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (c) 2008 Cyrille Berger - * Copyright (c) 2010 Geoffry Song - * Copyright (c) 2014 Wolthera van Hövell tot Westerflier - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "VanishingPointAssistant.h" - -#include "kis_debug.h" -#include - -#include -#include -#include - -#include -#include -#include - -#include - -VanishingPointAssistant::VanishingPointAssistant() - : KisPaintingAssistant("vanishing point", i18n("Vanishing Point assistant")) -{ -} - -QPointF VanishingPointAssistant::project(const QPointF& pt, const QPointF& strokeBegin) -{ - //Q_ASSERT(handles().size() == 1 || handles().size() == 5); - //code nicked from the perspective ruler. - qreal - dx = pt.x() - strokeBegin.x(), - dy = pt.y() - strokeBegin.y(); - if (dx * dx + dy * dy < 4.0) { - // allow some movement before snapping - return strokeBegin; - } - //dbgKrita<canvasWidget()->mapFromGlobal(QCursor::pos()); - } - else { - //...of course, you need to have access to a canvas-widget for that.// - mousePos = QCursor::pos();//this'll give an offset// - dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"< 0 && outline()==true && previewVisible==true) { - //don't draw if invalid. - QTransform initialTransform = converter->documentToWidgetTransform(); - QPointF startPoint = initialTransform.map(*handles()[0]); - - QLineF snapLine= QLineF(startPoint, mousePos); - QRect viewport= gc.viewport(); - - KisAlgebra2D::intersectLineRect(snapLine, viewport); - - QRect bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); - - QPainterPath path; - if (bounds.contains(startPoint.toPoint())){ - path.moveTo(startPoint); - path.lineTo(snapLine.p1()); - } - else - {path.moveTo(snapLine.p1()); - path.lineTo(snapLine.p2());} - - drawPreview(gc, path);//and we draw the preview. - } - gc.restore(); - - KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); - -} - -void VanishingPointAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) -{ - if (assistantVisible==false){return;} - if (handles().size() < 1) return; - - QTransform initialTransform = converter->documentToWidgetTransform(); - gc.setTransform(initialTransform); - QPointF p0 = *handles()[0]; - - QPainterPath path; - path.moveTo(QPointF(p0.x() - 10.0, p0.y() - 10.0)); path.lineTo(QPointF(p0.x() + 10.0, p0.y() + 10.0)); - path.moveTo(QPointF(p0.x() - 10.0, p0.y() + 10.0)); path.lineTo(QPointF(p0.x() + 10.0, p0.y() - 10.0)); - drawPath(gc, path, snapping()); - -} - -QPointF VanishingPointAssistant::buttonPosition() const -{ - return (*handles()[0]); -} - -VanishingPointAssistantFactory::VanishingPointAssistantFactory() -{ -} - -VanishingPointAssistantFactory::~VanishingPointAssistantFactory() -{ -} - -QString VanishingPointAssistantFactory::id() const -{ - return "vanishing point"; -} - -QString VanishingPointAssistantFactory::name() const -{ - return i18n("Vanishing Point"); -} - -KisPaintingAssistant* VanishingPointAssistantFactory::createPaintingAssistant() const -{ - return new VanishingPointAssistant; -} diff --git a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.h b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.h deleted file mode 100644 index 8f00a66469..0000000000 --- a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2008 Cyrille Berger - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef _KIS_RULER_ASSISTANT_TOOL_H_ -#define _KIS_RULER_ASSISTANT_TOOL_H_ - -#include - -#include -#include - -#include -#include "kis_painting_assistant.h" -#include -#include - -#include "ui_AssistantsToolOptions.h" - -class KisRulerAssistantTool : public KisTool -{ - Q_OBJECT - enum PerspectiveAssistantEditionMode { - MODE_CREATION, // This is the mode when there is not yet a perspective grid - MODE_EDITING, // This is the mode when the grid has been created, and we are waiting for the user to click on a control box - MODE_DRAGGING_NODE, // In this mode one node is translated - MODE_DRAGGING_TRANSLATING_TWONODES // This mode is used when creating a new sub perspective grid - }; -public: - KisRulerAssistantTool(KoCanvasBase * canvas); - ~KisRulerAssistantTool() override; - - virtual quint32 priority() { - return 3; - } - void beginPrimaryAction(KoPointerEvent *event) override; - void continuePrimaryAction(KoPointerEvent *event) override; - void endPrimaryAction(KoPointerEvent *event) override; - void mouseMoveEvent(KoPointerEvent *event) override; - - QWidget *createOptionWidget() override; - -private: - void addAssistant(); - void removeAssistant(KisPaintingAssistantSP assistant); - void snappingOn(KisPaintingAssistantSP assistant); - void snappingOff(KisPaintingAssistantSP assistant); - void outlineOn(KisPaintingAssistantSP assistant); - void outlineOff(KisPaintingAssistantSP assistant); - bool mouseNear(const QPointF& mousep, const QPointF& point); - KisPaintingAssistantHandleSP nodeNearPoint(KisPaintingAssistantSP grid, QPointF point); - QPointF snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers); - QPointF snapToGuide(const QPointF& pt, const QPointF &offset); - -public Q_SLOTS: - void activate(ToolActivation toolActivation, const QSet &shapes) override; - void deactivate() override; - -private Q_SLOTS: - void removeAllAssistants(); - void saveAssistants(); - void loadAssistants(); - -protected: - - void paint(QPainter& gc, const KoViewConverter &converter) override; - -protected: - QPointer m_canvas; - QList m_handles; - QList m_sideHandles; - KisPaintingAssistantHandleSP m_handleDrag; - KisPaintingAssistantHandleSP m_handleCombine; - KisPaintingAssistantSP m_assistantDrag; - KisPaintingAssistantSP m_newAssistant; - QPointF m_cursorStart; - QPointF m_currentAdjustment; - Ui::AssistantsToolOptions m_options; - QWidget* m_optionsWidget; - QPointF m_dragStart; - QLineF m_radius; - bool m_snapIsRadial; - QPointF m_dragEnd; - -private: - PerspectiveAssistantEditionMode m_internalMode; - qint32 m_handleSize, m_handleHalfSize; - KisPaintingAssistantHandleSP m_selectedNode1, m_selectedNode2, m_higlightedNode; - int m_assistantHelperYOffset; -}; - - -class KisRulerAssistantToolFactory : public KoToolFactoryBase -{ -public: - KisRulerAssistantToolFactory() - : KoToolFactoryBase("KisRulerAssistantTool") { - setToolTip(i18n("Assistant Tool")); - setSection(TOOL_TYPE_VIEW); - setIconName(koIconNameCStr("krita_tool_ruler_assistant")); - setPriority(0); - setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); - }; - - - ~KisRulerAssistantToolFactory() override {} - - KoToolBase * createTool(KoCanvasBase * canvas) override { - return new KisRulerAssistantTool(canvas); - } - -}; - - -#endif - diff --git a/plugins/assistants/RulerAssistant/krita_tool_ruler_assistant.png b/plugins/assistants/RulerAssistant/krita_tool_ruler_assistant.png deleted file mode 100644 index 876b1e0588..0000000000 Binary files a/plugins/assistants/RulerAssistant/krita_tool_ruler_assistant.png and /dev/null differ diff --git a/plugins/color/lcms2engine/CMakeLists.txt b/plugins/color/lcms2engine/CMakeLists.txt index bcd3983c9a..8b30a68278 100644 --- a/plugins/color/lcms2engine/CMakeLists.txt +++ b/plugins/color/lcms2engine/CMakeLists.txt @@ -1,97 +1,95 @@ project( lcmsengine ) add_subdirectory(tests) include_directories(SYSTEM - ${LCMS2_INCLUDE_DIR} - ${Boost_INCLUDE_DIRS} - + ${LCMS2_INCLUDE_DIR} ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/cmyk_u8 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/cmyk_u16 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/cmyk_f32 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/gray_u8 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/gray_u16 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/gray_f32 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/lab_u8 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/lab_u16 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/lab_f32 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/rgb_u8 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/rgb_u16 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/rgb_f32 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/xyz_u8 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/xyz_u16 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/xyz_f32 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/ycbcr_u8 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/ycbcr_u16 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/ycbcr_f32 ${CMAKE_CURRENT_SOURCE_DIR}/colorprofiles ) if (HAVE_LCMS24 AND OPENEXR_FOUND) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/gray_f16 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/rgb_f16 ${CMAKE_CURRENT_SOURCE_DIR}/colorspaces/xyz_f16 ) endif () set(FILE_OPENEXR_SOURCES) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() set ( lcmsengine_SRCS colorspaces/cmyk_u8/CmykU8ColorSpace.cpp colorspaces/cmyk_u16/CmykU16ColorSpace.cpp colorspaces/cmyk_f32/CmykF32ColorSpace.cpp colorspaces/gray_u8/GrayU8ColorSpace.cpp colorspaces/gray_u16/GrayU16ColorSpace.cpp colorspaces/gray_f32/GrayF32ColorSpace.cpp colorspaces/lab_u8/LabU8ColorSpace.cpp colorspaces/lab_u16/LabColorSpace.cpp colorspaces/lab_f32/LabF32ColorSpace.cpp colorspaces/xyz_u8/XyzU8ColorSpace.cpp colorspaces/xyz_u16/XyzU16ColorSpace.cpp colorspaces/xyz_f32/XyzF32ColorSpace.cpp colorspaces/rgb_u8/RgbU8ColorSpace.cpp colorspaces/rgb_u16/RgbU16ColorSpace.cpp colorspaces/rgb_f32/RgbF32ColorSpace.cpp colorspaces/ycbcr_u8/YCbCrU8ColorSpace.cpp colorspaces/ycbcr_u16/YCbCrU16ColorSpace.cpp colorspaces/ycbcr_f32/YCbCrF32ColorSpace.cpp colorprofiles/LcmsColorProfileContainer.cpp colorprofiles/IccColorProfile.cpp IccColorSpaceEngine.cpp LcmsColorSpace.cpp LcmsEnginePlugin.cpp ) if (HAVE_LCMS24 AND OPENEXR_FOUND) set ( lcmsengine_SRCS ${lcmsengine_SRCS} colorspaces/gray_f16/GrayF16ColorSpace.cpp colorspaces/rgb_f16/RgbF16ColorSpace.cpp colorspaces/xyz_f16/XyzF16ColorSpace.cpp ) endif () add_library(kritalcmsengine MODULE ${lcmsengine_SRCS}) target_link_libraries(kritalcmsengine kritapigment kritawidgetutils KF5::I18n KF5::CoreAddons ${LCMS2_LIBRARIES} ${LINK_OPENEXR_LIB}) install(TARGETS kritalcmsengine DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/animation/CMakeLists.txt b/plugins/dockers/animation/CMakeLists.txt index 874c62f6f1..dac45e43ba 100644 --- a/plugins/dockers/animation/CMakeLists.txt +++ b/plugins/dockers/animation/CMakeLists.txt @@ -1,57 +1,55 @@ if (NOT WIN32 AND NOT APPLE) add_subdirectory(tests) -endif() - -include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) +endif() set(KRITA_ANIMATIONDOCKER_SOURCES animation_dockers.cpp animation_docker.cpp timeline_docker.cpp onion_skins_docker.cpp timeline_layers_header.cpp timeline_ruler_header.cpp kis_time_based_item_model.cpp timeline_frames_model.cpp timeline_frames_view.cpp timeline_frames_item_delegate.cpp timeline_frames_index_converter.cpp timeline_node_list_keeper.cpp timeline_color_scheme.cpp kis_draggable_tool_button.cpp kis_zoom_button.cpp kis_animation_utils.cpp kis_custom_modifiers_catcher.cpp kis_equalizer_column.cpp kis_equalizer_slider.cpp kis_equalizer_button.cpp kis_equalizer_widget.cpp kis_animation_curve_docker.cpp kis_animation_curves_model.cpp kis_animation_curves_view.cpp kis_animation_curves_value_ruler.cpp kis_animation_curves_keyframe_delegate.cpp kis_animation_curve_channel_list_model.cpp kis_animation_curve_channel_list_delegate.cpp ) ki18n_wrap_ui(KRITA_ANIMATIONDOCKER_SOURCES wdg_animation.ui onion_skins_docker.ui wdg_animation_curves.ui ) add_library(kritaanimationdocker MODULE ${KRITA_ANIMATIONDOCKER_SOURCES}) generate_export_header(kritaanimationdocker BASE_NAME kritaanimationdocker EXPORT_MACRO_NAME KRITAANIMATIONDOCKER_EXPORT) target_link_libraries(kritaanimationdocker kritaui kritawidgets) install(TARGETS kritaanimationdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/griddocker/CMakeLists.txt b/plugins/dockers/griddocker/CMakeLists.txt index 20e7b1a3ee..4acc8a676a 100644 --- a/plugins/dockers/griddocker/CMakeLists.txt +++ b/plugins/dockers/griddocker/CMakeLists.txt @@ -1,13 +1,8 @@ -include_directories(SYSTEM - ${Boost_INCLUDE_DIRS} -) - - set(KRITA_GRIDDOCKER_SOURCES griddocker.cpp griddocker_dock.cpp grid_config_widget.cpp) ki18n_wrap_ui(KRITA_GRIDDOCKER_SOURCES grid_config_widget.ui ) add_library(kritagriddocker MODULE ${KRITA_GRIDDOCKER_SOURCES}) target_link_libraries(kritagriddocker kritaui) install(TARGETS kritagriddocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/lut/CMakeLists.txt b/plugins/dockers/lut/CMakeLists.txt index 8ed1b48a36..4b00e46112 100644 --- a/plugins/dockers/lut/CMakeLists.txt +++ b/plugins/dockers/lut/CMakeLists.txt @@ -1,25 +1,24 @@ if (NOT WIN32 AND NOT APPLE) add_subdirectory(tests) endif() include_directories(SYSTEM ${OCIO_INCLUDE_DIR} - ${Boost_INCLUDE_DIRS} ) set(KRITA_LUTDOCKER_SOURCES lutdocker.cpp lutdocker_dock.cpp ocio_display_filter.cpp black_white_point_chooser.cpp ) ki18n_wrap_ui(KRITA_LUTDOCKER_SOURCES wdglut.ui ) add_library(kritalutdocker MODULE ${KRITA_LUTDOCKER_SOURCES}) generate_export_header(kritalutdocker BASE_NAME kritalutdocker) target_link_libraries(kritalutdocker kritaui ${Boost_SYSTEM_LIBRARY} ${OCIO_LIBRARIES}) install(TARGETS kritalutdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/lut/tests/CMakeLists.txt b/plugins/dockers/lut/tests/CMakeLists.txt index 42a8985562..bc68b39399 100644 --- a/plugins/dockers/lut/tests/CMakeLists.txt +++ b/plugins/dockers/lut/tests/CMakeLists.txt @@ -1,12 +1,11 @@ macro_add_unittest_definitions() include_directories(${CMAKE_SOURCE_DIR}/sdk/tests) include_directories(SYSTEM ${OCIO_INCLUDE_DIR} - ${Boost_INCLUDE_DIRS} ) ########### next target ############### krita_add_broken_unit_test(kis_ocio_display_filter_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ocio-KisOcioDisplayFilterTest LINK_LIBRARIES kritaimage kritaui kritalutdocker ${OCIO_LIBRARIES} KF5::I18n Qt5::Test) diff --git a/plugins/extensions/pykrita/plugin/plugins/tenscripts/kritapykrita_tenscripts.desktop b/plugins/extensions/pykrita/plugin/plugins/tenscripts/kritapykrita_tenscripts.desktop index 89578d07c6..330a0c61d2 100644 --- a/plugins/extensions/pykrita/plugin/plugins/tenscripts/kritapykrita_tenscripts.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/tenscripts/kritapykrita_tenscripts.desktop @@ -1,23 +1,25 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenscripts X-Python-2-Compatible=false Name=Ten Scripts Name[ca]=Deu scripts Name[ca@valencia]=Deu scripts Name[en_GB]=Ten Scripts +Name[es]=Diez guiones Name[nl]=Tien scripts Name[pt]=Dez Programas Name[sv]=Tio skript Name[uk]=Десять скриптів Name[x-test]=xxTen Scriptsxx Comment=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[ca]=Un connector basant en el Python per crear deu accions i assignar-les a scripts del Python Comment[ca@valencia]=Un connector basant en el Python per crear deu accions i assignar-les a scripts del Python Comment[en_GB]=A Python-based plugin for creating ten actions and assign them to Python scripts +Comment[es]=Un complemento basado en Python para crear diez acciones y asignarlas a guiones de Python Comment[nl]=Een op Python gebaseerde plug-in voor aanmaken van tien acties en ze dan toewijzen aan Python-scripts Comment[pt]=Um 'plugin' feito em Python para criar dez acções e atribuí-las a programas em Python Comment[sv]=Ett Python-baserat insticksprogram för att skapa tio åtgärder och tilldela dem till Python-skript Comment[uk]=Скрипт на основі Python для створення десяти дій і прив'язування до них скриптів Python Comment[x-test]=xxA Python-based plugin for creating ten actions and assign them to Python scriptsxx diff --git a/plugins/filters/levelfilter/kis_level_filter.cpp b/plugins/filters/levelfilter/kis_level_filter.cpp index 35e8db9021..25ba596fb7 100644 --- a/plugins/filters/levelfilter/kis_level_filter.cpp +++ b/plugins/filters/levelfilter/kis_level_filter.cpp @@ -1,312 +1,344 @@ /* * This file is part of Krita * * Copyright (c) 2006 Frederic Coiffier * * 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_level_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_paint_device.h" #include "kis_histogram.h" #include "kis_painter.h" #include "kis_gradient_slider.h" #include "kis_processing_information.h" #include "kis_selection.h" #include "kis_types.h" #include "filter/kis_color_transformation_configuration.h" KisLevelFilter::KisLevelFilter() : KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Levels...")) { setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L)); setSupportsPainting(false); setColorSpaceIndependence(TO_LAB16); } KisLevelFilter::~KisLevelFilter() { } KisConfigWidget * KisLevelFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const { return new KisLevelConfigWidget(parent, dev); } KoColorTransformation* KisLevelFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { if (!config) { warnKrita << "No configuration object for level filter\n"; return 0; } Q_ASSERT(config); int blackvalue = config->getInt("blackvalue"); int whitevalue = config->getInt("whitevalue", 255); double gammavalue = config->getDouble("gammavalue", 1.0); int outblackvalue = config->getInt("outblackvalue"); int outwhitevalue = config->getInt("outwhitevalue", 255); quint16 transfer[256]; for (int i = 0; i < 256; i++) { if (i <= blackvalue) transfer[i] = outblackvalue; else if (i < whitevalue) { double a = (double)(i - blackvalue) / (double)(whitevalue - blackvalue); a = (double)(outwhitevalue - outblackvalue) * pow(a, (1.0 / gammavalue)); transfer[i] = int(outblackvalue + a); } else transfer[i] = outwhitevalue; // TODO use floats instead of integer in the configuration transfer[i] = ((int)transfer[i] * 0xFFFF) / 0xFF ; } return cs->createBrightnessContrastAdjustment(transfer); } KisLevelConfigWidget::KisLevelConfigWidget(QWidget * parent, KisPaintDeviceSP dev) : KisConfigWidget(parent) { Q_ASSERT(dev); m_page.setupUi(this); m_page.ingradient->enableGamma(true); m_page.blackspin->setValue(0); m_page.whitespin->setValue(255); m_page.gammaspin->setValue(1.0); m_page.ingradient->slotModifyGamma(1.0); m_page.outblackspin->setValue(0); m_page.outwhitespin->setValue(255); connect(m_page.blackspin, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page.whitespin, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page.ingradient, SIGNAL(sigModifiedGamma(double)), SIGNAL(sigConfigurationItemChanged())); connect(m_page.blackspin, SIGNAL(valueChanged(int)), m_page.ingradient, SLOT(slotModifyBlack(int))); connect(m_page.whitespin, SIGNAL(valueChanged(int)), m_page.ingradient, SLOT(slotModifyWhite(int))); connect(m_page.gammaspin, SIGNAL(valueChanged(double)), m_page.ingradient, SLOT(slotModifyGamma(double))); connect(m_page.blackspin, SIGNAL(valueChanged(int)), this, SLOT(slotModifyInWhiteLimit(int))); connect(m_page.whitespin, SIGNAL(valueChanged(int)), this, SLOT(slotModifyInBlackLimit(int))); connect(m_page.ingradient, SIGNAL(sigModifiedBlack(int)), m_page.blackspin, SLOT(setValue(int))); connect(m_page.ingradient, SIGNAL(sigModifiedWhite(int)), m_page.whitespin, SLOT(setValue(int))); connect(m_page.ingradient, SIGNAL(sigModifiedGamma(double)), m_page.gammaspin, SLOT(setValue(double))); connect(m_page.outblackspin, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page.outwhitespin, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page.outblackspin, SIGNAL(valueChanged(int)), m_page.outgradient, SLOT(slotModifyBlack(int))); connect(m_page.outwhitespin, SIGNAL(valueChanged(int)), m_page.outgradient, SLOT(slotModifyWhite(int))); connect(m_page.outblackspin, SIGNAL(valueChanged(int)), this, SLOT(slotModifyOutWhiteLimit(int))); connect(m_page.outwhitespin, SIGNAL(valueChanged(int)), this, SLOT(slotModifyOutBlackLimit(int))); connect(m_page.outgradient, SIGNAL(sigModifiedBlack(int)), m_page.outblackspin, SLOT(setValue(int))); connect(m_page.outgradient, SIGNAL(sigModifiedWhite(int)), m_page.outwhitespin, SLOT(setValue(int))); connect(m_page.butauto, SIGNAL(clicked(bool)), this, SLOT(slotAutoLevel(void))); + connect(m_page.butinvert, SIGNAL(clicked(bool)), this, SLOT(slotInvert(void))); connect((QObject*)(m_page.chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(slotDrawHistogram(bool))); KoHistogramProducer *producer = new KoGenericLabHistogramProducer(); m_histogram.reset( new KisHistogram(dev, dev->exactBounds(), producer, LINEAR) ); m_histlog = false; m_page.histview->resize(288,100); + m_inverted = false; slotDrawHistogram(); } KisLevelConfigWidget::~KisLevelConfigWidget() { } void KisLevelConfigWidget::slotDrawHistogram(bool logarithmic) { int wHeight = m_page.histview->height(); int wHeightMinusOne = wHeight - 1; int wWidth = m_page.histview->width(); if (m_histlog != logarithmic) { // Update the m_histogram if (logarithmic) m_histogram->setHistogramType(LOGARITHMIC); else m_histogram->setHistogramType(LINEAR); m_histlog = logarithmic; } QPalette appPalette = QApplication::palette(); QPixmap pix(wWidth-100, wHeight); pix.fill(QColor(appPalette.color(QPalette::Base))); QPainter p(&pix); p.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); double highest = (double)m_histogram->calculations().getHighest(); qint32 bins = m_histogram->producer()->numberOfBins(); // use nearest neighbour interpolation if (m_histogram->getHistogramType() == LINEAR) { double factor = (double)(wHeight - wHeight / 5.0) / highest; for (int i = 0; i < wWidth; i++) { int binNo = qRound((double)i / wWidth * (bins - 1)); if ((int)m_histogram->getValue(binNo) != 0) p.drawLine(i, wHeightMinusOne, i, wHeightMinusOne - (int)m_histogram->getValue(binNo) * factor); } } else { double factor = (double)(wHeight - wHeight / 5.0) / (double)log(highest); for (int i = 0; i < wWidth; i++) { int binNo = qRound((double)i / wWidth * (bins - 1)) ; if ((int)m_histogram->getValue(binNo) != 0) p.drawLine(i, wHeightMinusOne, i, wHeightMinusOne - log((double)m_histogram->getValue(binNo)) * factor); } } m_page.histview->setPixmap(pix); } void KisLevelConfigWidget::slotModifyInBlackLimit(int limit) { m_page.blackspin->setMaximum(limit - 1); } void KisLevelConfigWidget::slotModifyInWhiteLimit(int limit) { m_page.whitespin->setMinimum(limit + 1); } void KisLevelConfigWidget::slotModifyOutBlackLimit(int limit) { - m_page.outblackspin->setMaximum(limit - 1); + if (m_inverted) { + m_page.outblackspin->setMinimum(limit + 1); + } else { + m_page.outblackspin->setMaximum(limit - 1); + } } void KisLevelConfigWidget::slotModifyOutWhiteLimit(int limit) { - m_page.outwhitespin->setMinimum(limit + 1); + if (m_inverted) { + m_page.outwhitespin->setMaximum(limit - 1); + } else { + m_page.outwhitespin->setMinimum(limit + 1); + } } void KisLevelConfigWidget::slotAutoLevel(void) { Q_ASSERT(m_histogram); qint32 num_bins = m_histogram->producer()->numberOfBins(); Q_ASSERT(num_bins > 1); int chosen_low_bin = 0, chosen_high_bin = num_bins-1; int count_thus_far = m_histogram->getValue(0); const int total_count = m_histogram->producer()->count(); const double threshold = 0.006; // find the low and hi point/bins based on summing count percentages // // this implementation is a port of GIMP's auto level implementation // (use a GPLv2 version as reference, specifically commit 51bfd07f18ef045a3e43632218fd92cae9ff1e48) for (int bin=0; bin<(num_bins-1); ++bin) { int next_count_thus_far = count_thus_far + m_histogram->getValue(bin+1); double this_percentage = static_cast(count_thus_far) / total_count; double next_percentage = static_cast(next_count_thus_far) / total_count; //dbgKrita << "bin" << bin << "this_percentage" << this_percentage << "next_percentage" << next_percentage; if (fabs(this_percentage - threshold) < fabs(next_percentage - threshold)) { chosen_low_bin = bin; break; } count_thus_far = next_count_thus_far; } count_thus_far = m_histogram->getValue(num_bins-1); for (int bin=(num_bins-1); bin>0; --bin) { int next_count_thus_far = count_thus_far + m_histogram->getValue(bin-1); double this_percentage = static_cast(count_thus_far) / total_count; double next_percentage = static_cast(next_count_thus_far) / total_count; //dbgKrita << "hi-bin" << bin << "this_percentage" << this_percentage << "next_percentage" << next_percentage; if (fabs(this_percentage - threshold) < fabs(next_percentage - threshold)) { chosen_high_bin = bin; break; } count_thus_far = next_count_thus_far; } if (chosen_low_bin < chosen_high_bin) { m_page.blackspin->setValue(chosen_low_bin); m_page.ingradient->slotModifyBlack(chosen_low_bin); m_page.whitespin->setValue(chosen_high_bin); m_page.ingradient->slotModifyWhite(chosen_high_bin); } } +void KisLevelConfigWidget::slotInvert(void) +{ + m_inverted = !m_inverted; + int white = m_page.outwhitespin->value(); + int black = m_page.outblackspin->value(); + + resetOutSpinLimit(); + m_page.outgradient->setInverted(m_inverted); + m_page.outwhitespin->setValue(black); + m_page.outblackspin->setValue(white); +} + KisPropertiesConfigurationSP KisLevelConfigWidget::configuration() const { KisColorTransformationConfiguration * config = new KisColorTransformationConfiguration(KisLevelFilter::id().id(), 1); config->setProperty("blackvalue", m_page.blackspin->value()); config->setProperty("whitevalue", m_page.whitespin->value()); config->setProperty("gammavalue", m_page.gammaspin->value()); config->setProperty("outblackvalue", m_page.outblackspin->value()); config->setProperty("outwhitevalue", m_page.outwhitespin->value()); return config; } void KisLevelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { QVariant value; if (config->getProperty("blackvalue", value)) { m_page.blackspin->setValue(value.toUInt()); m_page.ingradient->slotModifyBlack(value.toUInt()); } if (config->getProperty("whitevalue", value)) { m_page.whitespin->setValue(value.toUInt()); m_page.ingradient->slotModifyWhite(value.toUInt()); } if (config->getProperty("gammavalue", value)) { m_page.gammaspin->setValue(value.toUInt()); m_page.ingradient->slotModifyGamma(value.toDouble()); } if (config->getProperty("outblackvalue", value)) { m_page.outblackspin->setValue(value.toUInt()); m_page.outgradient->slotModifyBlack(value.toUInt()); } if (config->getProperty("outwhitevalue", value)) { m_page.outwhitespin->setValue(value.toUInt()); m_page.outgradient->slotModifyWhite(value.toUInt()); } } + +void KisLevelConfigWidget::resetOutSpinLimit() { + if (m_inverted) { + m_page.outblackspin->setMaximum(255); + m_page.outwhitespin->setMinimum(0); + } else { + m_page.outblackspin->setMinimum(0); + m_page.outwhitespin->setMaximum(255); + } +} diff --git a/plugins/filters/levelfilter/kis_level_filter.h b/plugins/filters/levelfilter/kis_level_filter.h index 4d581d577b..6655fb2516 100644 --- a/plugins/filters/levelfilter/kis_level_filter.h +++ b/plugins/filters/levelfilter/kis_level_filter.h @@ -1,84 +1,88 @@ /* * This file is part of Krita * * Copyright (c) 2006 Frederic Coiffier * * 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_LEVEL_FILTER_H_ #define _KIS_LEVEL_FILTER_H_ #include "filter/kis_color_transformation_filter.h" #include "kis_config_widget.h" #include "ui_wdg_level.h" class WdgLevel; class QWidget; class KisHistogram; /** * This class affect Intensity Y of the image */ class KisLevelFilter : public KisColorTransformationFilter { public: KisLevelFilter(); ~KisLevelFilter() override; public: // virtual KisFilterConfigurationSP factoryConfiguration() const; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override; KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; static inline KoID id() { return KoID("levels", i18n("Levels")); } }; class KisLevelConfigWidget : public KisConfigWidget { Q_OBJECT public: KisLevelConfigWidget(QWidget * parent, KisPaintDeviceSP dev); ~KisLevelConfigWidget() override; KisPropertiesConfigurationSP configuration() const override; void setConfiguration(const KisPropertiesConfigurationSP config) override; Ui::WdgLevel m_page; protected Q_SLOTS: void slotDrawHistogram(bool logarithmic = false); void slotModifyInBlackLimit(int); void slotModifyInWhiteLimit(int); void slotModifyOutBlackLimit(int); void slotModifyOutWhiteLimit(int); void slotAutoLevel(void); + void slotInvert(void); + + void resetOutSpinLimit(); protected: QScopedPointer m_histogram; bool m_histlog; + bool m_inverted; }; #endif diff --git a/plugins/filters/levelfilter/wdg_level.ui b/plugins/filters/levelfilter/wdg_level.ui index faa3ea90a7..a873f063ee 100644 --- a/plugins/filters/levelfilter/wdg_level.ui +++ b/plugins/filters/levelfilter/wdg_level.ui @@ -1,344 +1,351 @@ WdgLevel 0 0 263 344 0 0 0 0 600 32767 Levels 0 0 0 0 <b>Input Levels</b> false Qt::RightToLeft Logarithmic 0 5 5 200 100 16777215 300 0 true Qt::AlignJustify|Qt::AlignVCenter false 0 -1 256 24 QAbstractSpinBox::PlusMinus 255 Qt::Horizontal QSizePolicy::MinimumExpanding 25 20 QAbstractSpinBox::PlusMinus 3 0.001000000000000 10.000000000000000 0.010000000000000 1.000000000000000 Qt::Horizontal QSizePolicy::MinimumExpanding 25 20 QAbstractSpinBox::PlusMinus 255 Qt::Vertical QSizePolicy::Fixed 0 0 <b>Output Levels</b> false 256 24 QAbstractSpinBox::PlusMinus 255 Qt::Horizontal QSizePolicy::MinimumExpanding 50 20 QAbstractSpinBox::PlusMinus 255 &Auto Levels + + + + &Invert + + + Qt::Horizontal 40 20 Qt::Vertical 20 0 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisGradientSlider QWidget
kis_gradient_slider.h
KisDoubleParseSpinBox QDoubleSpinBox
kis_double_parse_spin_box.h
kis_gradient_slider.h kis_gradient_slider.h
diff --git a/plugins/impex/csv/CMakeLists.txt b/plugins/impex/csv/CMakeLists.txt index 6cd719e02f..60eba64463 100644 --- a/plugins/impex/csv/CMakeLists.txt +++ b/plugins/impex/csv/CMakeLists.txt @@ -1,36 +1,32 @@ add_subdirectory(tests) -include_directories(SYSTEM - ${Boost_INCLUDE_DIRS} -) - # import set(kritacsvimport_SOURCES kis_csv_import.cpp csv_loader.cpp csv_read_line.cpp csv_layer_record.cpp ) add_library(kritacsvimport MODULE ${kritacsvimport_SOURCES}) target_link_libraries(kritacsvimport kritaui ) install(TARGETS kritacsvimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) # export set(kritacsvexport_SOURCES kis_csv_export.cpp csv_saver.cpp csv_layer_record.cpp ) add_library(kritacsvexport MODULE ${kritacsvexport_SOURCES}) target_link_libraries(kritacsvexport kritaui kritaimpex) install(TARGETS kritacsvexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_csv.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/jpeg/kis_wdg_options_jpeg.ui b/plugins/impex/jpeg/kis_wdg_options_jpeg.ui index d45e569113..8a8f5549a8 100644 --- a/plugins/impex/jpeg/kis_wdg_options_jpeg.ui +++ b/plugins/impex/jpeg/kis_wdg_options_jpeg.ui @@ -1,388 +1,388 @@ WdgOptionsJPEG 0 0 545 390 - 2 + 0 Basic Progressive Force convert to sRGB 20 0 0 0 0 <html><head/><body><p>These settings determine how much information is lost during compression. Low: small files, bad quality. High: big files, good quality.</p></body></html> 0 0 Quality 0 0 Transparent pixel fill color: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 25 0 <html><head/><body><p>Background color to replace transparent pixels with.</p></body></html> Qt::Vertical 20 40 Save ICC Profile false Advanced quality 20 0 0 60 20 0 0 Smooth: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Subsampling: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 2x2, 1x1, 1x1 (smallest file) 2x1, 1x1, 1x1 1x2, 1x1, 1x1 1x1, 1x1, 1x1 (best quality) Force baseline JPEG true Optimize true Qt::Vertical 20 40 Metadata Formats: Exif true IPTC true XMP true Qt::Vertical 20 40 Qt::Vertical 505 16 <html><head/><body><p>Store document metadata that is in the document information. This will override any layer metadata.</p></body></html> Store Document Metadata Filters: <html><head/><body><p>Add the author nickname and the first contact of the author profile. This is overridden by the anonymizer.</p></body></html> Sign with Author Profile Data KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisColorButton QPushButton
kis_color_button.h
tabWidget progressive baseLineJPEG metaDataFilters exif iptc xmp
diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index e20f2947d7..4b364cf4f4 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1173 +1,1174 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * 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 "kis_kra_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resource_server_provider.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QStringList errorMessages; QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; QString name; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { image = new KisImage(m_d->document->createUndoStore(), width, height, cs, name); } else { image = new KisImage(0, width, height, cs, name); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); KisProofingConfigurationSP proofingConfig = image->proofingConfiguration(); if (!proofingConfig) { proofingConfig = KisImageConfig().defaultProofingconfiguration(); } if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData); if (proofingProfile->valid()){ KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. KisKraLoadVisitor visitor(image, store, m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } if (!visitor.warningMessages().isEmpty()) { m_d->warningMessages.append(visitor.warningMessages()); } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } QStringList KisKraLoader::warningMessages() const { return m_d->warningMessages; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); + //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image, parent); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element, parent); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element, parent); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element, parent); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element, parent); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, parent, colorSpace); else if (nodeType == FILE_LAYER) { node = loadFileLayer(element, image, name, opacity); } else { m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; node->setUseInTimeline(timelineEnabled); if (node->inherits("KisPaintLayer")) { KisPaintLayer* layer = qobject_cast(node.data()); QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); layer->setChannelLockFlags(channelLockFlags); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = QDir(basePath).filePath(QDir::cleanPath(filename)); // Entering the event loop to show the messagebox will delete the image, so up the ref by one image->ref(); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; QString legacy = filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } //get deprecated filters. if (filtername=="brightnesscontrast") { legacy = filtername; filtername = "perchannel"; } if (filtername=="left edge detections" || filtername=="right edge detections" || filtername=="top edge detections" || filtername=="bottom edge detections") { legacy = filtername; filtername = "edge detection"; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); kfc->setProperty("legacy", legacy); if (legacy=="brightnesscontrast") { kfc->setProperty("colorModel", cs->colorModelId().id()); } // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KoShapeBasedDocumentBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } KisFilterConfigurationSP kgc = generator->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisCloneInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisCloneInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisCloneInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent, const KoColorSpace *colorSpace) { Q_UNUSED(parent); KisColorizeMaskSP mask = new KisColorizeMask(); bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp index 857f9beb98..fe87574833 100644 --- a/plugins/impex/libkra/kis_kra_saver.cpp +++ b/plugins/impex/libkra/kis_kra_saver.cpp @@ -1,452 +1,452 @@ /* This file is part of the KDE project * Copyright 2008 (C) Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_saver.h" #include "kis_kra_tags.h" #include "kis_kra_save_visitor.h" #include "kis_kra_savexml_visitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include #include "kis_dom_utils.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "KisProofingConfiguration.h" #include #include using namespace KRA; struct KisKraSaver::Private { public: KisDocument* doc; QMap nodeFileNames; QMap keyframeFilenames; QString imageName; QStringList errorMessages; }; KisKraSaver::KisKraSaver(KisDocument* document) : m_d(new Private) { m_d->doc = document; m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title"); if (m_d->imageName.isEmpty()) { m_d->imageName = i18n("Unnamed"); } } KisKraSaver::~KisKraSaver() { delete m_d; } QDomElement KisKraSaver::saveXML(QDomDocument& doc, KisImageSP image) { QDomElement imageElement = doc.createElement("IMAGE"); // Legacy! Q_ASSERT(image); imageElement.setAttribute(NAME, m_d->imageName); imageElement.setAttribute(MIME, NATIVE_MIMETYPE); imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width())); imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height())); imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id()); imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment")); // XXX: Save profile as blob inside the image, instead of the product name. if (image->profile() && image->profile()-> valid()) { imageElement.setAttribute(PROFILE, image->profile()->name()); } imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0)); imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0)); //now the proofing options: if (image->proofingConfiguration()) { imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile)); imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel)); imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth)); imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent)); imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState)); } quint32 count = 1; // We don't save the root layer, but it does count KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->doc->url().toLocalFile(), true); visitor.setSelectedNodes({m_d->doc->preActivatedNode()}); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); m_d->nodeFileNames = visitor.nodeFileNames(); m_d->keyframeFilenames = visitor.keyframeFileNames(); saveBackgroundColor(doc, imageElement, image); saveWarningColor(doc, imageElement, image); saveCompositions(doc, imageElement, image); saveAssistantsList(doc, imageElement); saveGrid(doc,imageElement); saveGuides(doc,imageElement); saveAudio(doc,imageElement); QDomElement animationElement = doc.createElement("animation"); KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate()); KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->fullClipRange()); KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime()); imageElement.appendChild(animationElement); return imageElement; } bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external) { QMap::iterator it; for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) { const KisNode *node = it.key(); QString filename = it.value(); QString location = (external ? QString() : uri) + m_d->imageName + LAYER_PATH + filename; if (!saveNodeKeyframes(store, location, node)) { return false; } } return true; } bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node) { QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0"); QDomElement root = doc.documentElement(); KisKeyframeChannel *channel; Q_FOREACH (channel, node->keyframeChannels()) { QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]); root.appendChild(element); } if (store->open(location)) { QByteArray xml = doc.toByteArray(); store->write(xml); store->close(); } else { m_d->errorMessages << i18n("could not save keyframes"); return false; } return true; } bool KisKraSaver::saveBinaryData(KoStore* store, KisImageSP image, const QString &uri, bool external, bool autosave) { QString location; // Save the layers data KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames); if (external) visitor.setExternalUri(uri); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); if (!m_d->errorMessages.isEmpty()) { return false; } // saving annotations // XXX this only saves EXIF and ICC info. This would probably need // a redesign of the dtd of the krita file to do this more generally correct // e.g. have tags or so. KisAnnotationSP annotation = image->annotation("exif"); if (annotation) { location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } if (image->profile()) { const KoColorProfile *profile = image->profile(); KisAnnotationSP annotation; if (profile) { QByteArray profileRawData = profile->rawData(); if (!profileRawData.isEmpty()) { if (profile->type() == "icc") { annotation = new KisAnnotation(ICC, profile->name(), profile->rawData()); } else { annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData()); } } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } //This'll embed the profile used for proofing into the kra file. if (image->proofingConfiguration()) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile); if (proofingProfile && proofingProfile->valid()) { QByteArray proofingProfileRaw = proofingProfile->rawData(); if (!proofingProfileRaw.isEmpty()) { annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData()); } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } { KisPSDLayerStyleCollectionResource collection("not-nexists.asl"); KIS_ASSERT_RECOVER_NOOP(!collection.valid()); collection.collectAllLayerStyles(image->root()); if (collection.valid()) { location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->open(location)) { QBuffer aslBuffer; aslBuffer.open(QIODevice::WriteOnly); collection.saveToDevice(&aslBuffer); aslBuffer.close(); store->write(aslBuffer.buffer()); store->close(); } } } if (!autosave) { KisPaintDeviceSP dev = image->projection(); KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); } saveAssistants(store, uri,external); return true; } QStringList KisKraSaver::errorMessages() const { return m_d->errorMessages; } void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR); KoColor color = image->defaultProjectionColor(); QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64())); element.appendChild(e); } void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (image->proofingConfiguration()) { QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR); KoColor color = image->proofingConfiguration()->warningColor; color.toXML(doc, e); element.appendChild(e); } } void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (!image->compositions().isEmpty()) { QDomElement e = doc.createElement("compositions"); Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) { composition->save(doc, e); } element.appendChild(e); } } bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external) { QString location; QMap assistantcounters; QByteArray data; QList assistants = m_d->doc->assistants(); QMap handlemap; if (!assistants.isEmpty()) { Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (!assistantcounters.contains(assist->id())){ assistantcounters.insert(assist->id(),0); } location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]); data = assist->saveXml(handlemap); store->open(location); store->write(data); store->close(); assistantcounters[assist->id()]++; } } return true; } bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element) { int count_ellipse = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0; QList assistants = m_d->doc->assistants(); if (!assistants.isEmpty()) { QDomElement assistantsElement = doc.createElement("assistants"); Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (assist->id() == "ellipse"){ assist->saveXmlList(doc, assistantsElement, count_ellipse); count_ellipse++; } else if (assist->id() == "spline"){ assist->saveXmlList(doc, assistantsElement, count_spline); count_spline++; } else if (assist->id() == "perspective"){ assist->saveXmlList(doc, assistantsElement, count_perspective); count_perspective++; } else if (assist->id() == "vanishing point"){ assist->saveXmlList(doc, assistantsElement, count_vanishingpoint); count_vanishingpoint++; } else if (assist->id() == "infinite ruler"){ assist->saveXmlList(doc, assistantsElement, count_infiniteruler); count_infiniteruler++; } else if (assist->id() == "parallel ruler"){ assist->saveXmlList(doc, assistantsElement, count_parallelruler); count_parallelruler++; } else if (assist->id() == "concentric ellipse"){ assist->saveXmlList(doc, assistantsElement, count_concentricellipse); count_concentricellipse++; } else if (assist->id() == "fisheye-point"){ assist->saveXmlList(doc, assistantsElement, count_fisheyepoint); count_fisheyepoint++; } else if (assist->id() == "ruler"){ assist->saveXmlList(doc, assistantsElement, count_ruler); count_ruler++; } } element.appendChild(assistantsElement); } return true; } bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element) { KisGridConfig config = m_d->doc->gridConfig(); if (!config.isDefault()) { QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid"); element.appendChild(gridElement); } return true; } bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element) { KisGuidesConfig guides = m_d->doc->guidesConfig(); - if (guides.hasGuides()) { + if (!guides.isDefault()) { QDomElement guidesElement = guides.saveToXml(doc, "guides"); element.appendChild(guidesElement); } return true; } bool KisKraSaver::saveAudio(QDomDocument& doc, QDomElement& element) { const KisImageAnimationInterface *interface = m_d->doc->image()->animationInterface(); QString fileName = interface->audioChannelFileName(); if (fileName.isEmpty()) return true; if (!QFileInfo::exists(fileName)) { m_d->errorMessages << i18n("Audio channel file %1 doesn't exist!", fileName); return false; } const QDir documentDir = QFileInfo(m_d->doc->localFilePath()).absoluteDir(); KIS_ASSERT_RECOVER_RETURN_VALUE(documentDir.exists(), false); fileName = documentDir.relativeFilePath(fileName); fileName = QDir::fromNativeSeparators(fileName); KIS_ASSERT_RECOVER_RETURN_VALUE(!fileName.isEmpty(), false); QDomElement audioElement = doc.createElement("audio"); KisDomUtils::saveValue(&audioElement, "masterChannelPath", fileName); KisDomUtils::saveValue(&audioElement, "audioMuted", interface->isAudioMuted()); KisDomUtils::saveValue(&audioElement, "audioVolume", interface->audioVolume()); element.appendChild(audioElement); return true; } diff --git a/plugins/impex/psd/CMakeLists.txt b/plugins/impex/psd/CMakeLists.txt index 3a46d19387..4112536da2 100644 --- a/plugins/impex/psd/CMakeLists.txt +++ b/plugins/impex/psd/CMakeLists.txt @@ -1,66 +1,65 @@ if (NOT MSVC AND NOT APPLE) add_subdirectory(tests) endif() configure_file(config_psd.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_psd.h) include_directories( ${CMAKE_BINARY_DIR}/libs/psd ${CMAKE_SOURCE_DIR}/libs/psd ) #For kispsd_include.h include_directories(SYSTEM ${ZLIB_INCLUDE_DIR} - ${Boost_INCLUDE_DIRS} ) set(LIB_PSD_SRCS psd_header.cpp psd_colormode_block.cpp psd_resource_section.cpp psd_resource_block.cpp psd_layer_section.cpp psd_layer_record.cpp psd_image_data.cpp psd_pixel_utils.cpp psd_additional_layer_info_block.cpp ) # # import # set(kritapsdimport_SOURCES psd_import.cc psd_loader.cpp ${LIB_PSD_SRCS} ) add_library(kritapsdimport MODULE ${kritapsdimport_SOURCES}) target_link_libraries(kritapsdimport kritaglobal kritaui kritapsd KF5::I18n ${ZLIB_LIBRARIES}) install(TARGETS kritapsdimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) # # export # set(kritapsdexport_SOURCES psd_export.cc psd_saver.cpp ${LIB_PSD_SRCS} ) add_library(kritapsdexport MODULE ${kritapsdexport_SOURCES}) if (MSVC) target_link_libraries(kritapsdexport kritaui kritapsd kritaimpex ${WIN32_PLATFORM_NET_LIBS} ${ZLIB_LIBRARIES}) else () target_link_libraries(kritapsdexport kritaui kritapsd kritaimpex ${ZLIB_LIBRARIES}) endif () install(TARGETS kritapsdexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_psd.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/psd/psd_additional_layer_info_block.cpp b/plugins/impex/psd/psd_additional_layer_info_block.cpp index c217f757e7..f83def914f 100644 --- a/plugins/impex/psd/psd_additional_layer_info_block.cpp +++ b/plugins/impex/psd/psd_additional_layer_info_block.cpp @@ -1,413 +1,416 @@ /* * Copyright (c) 2014 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_additional_layer_info_block.h" #include #include #include #include #include #include #include PsdAdditionalLayerInfoBlock::PsdAdditionalLayerInfoBlock(const PSDHeader& header) : m_header(header) { } void PsdAdditionalLayerInfoBlock::setExtraLayerInfoBlockHandler(ExtraLayerInfoBlockHandler handler) { m_layerInfoBlockHandler = handler; } bool PsdAdditionalLayerInfoBlock::read(QIODevice *io) { bool result = true; try { readImpl(io); } catch (KisAslReaderUtils::ASLParseException &e) { error = e.what(); result = false; } return result; } void PsdAdditionalLayerInfoBlock::readImpl(QIODevice* io) { using namespace KisAslReaderUtils; QStringList longBlocks; if (m_header.version > 1) { longBlocks << "LMsk" << "Lr16" << "Layr" << "Mt16" << "Mtrn" << "Alph"; } while (!io->atEnd()) { { const quint32 refSignature1 = 0x3842494D; // '8BIM' in little-endian const quint32 refSignature2 = 0x38423634; // '8B64' in little-endian if (!TRY_READ_SIGNATURE_2OPS_EX(io, refSignature1, refSignature2)) { break; } } QString key = readFixedString(io); dbgFile << "found info block with key" << key; quint64 blockSize = GARBAGE_VALUE_MARK; if (longBlocks.contains(key)) { SAFE_READ_EX(io, blockSize); } else { quint32 size32; SAFE_READ_EX(io, size32); blockSize = size32; } // offset verifier will correct the position on the exit from // current namespace, including 'continue', 'return' and // exceptions. SETUP_OFFSET_VERIFIER(infoBlockEndVerifier, io, blockSize, 0); if (keys.contains(key)) { error = "Found duplicate entry for key "; continue; } keys << key; // TODO: Loading of 32 bit files is not supported yet if (key == "Lr16"/* || key == "Lr32"*/) { if (m_layerInfoBlockHandler) { int offset = m_header.version > 1 ? 8 : 4; io->seek(io->pos() - offset); m_layerInfoBlockHandler(io); } } else if (key == "SoCo") { } else if (key == "GdFl") { } else if (key == "PtFl") { } else if (key == "brit") { } else if (key == "levl") { } else if (key == "curv") { } else if (key == "expA") { } else if (key == "vibA") { } else if (key == "hue") { } else if (key == "hue2") { } else if (key == "blnc") { } else if (key == "blwh") { } else if (key == "phfl") { } else if (key == "mixr") { } else if (key == "clrL") { } else if (key == "nvrt") { } else if (key == "post") { } else if (key == "thrs") { } else if (key == "grdm") { } else if (key == "selc") { } else if (key == "lrFX") { // deprecated! use lfx2 instead! } else if (key == "tySh") { } else if (key == "luni") { // get the unicode layer name unicodeLayerName = readUnicodeString(io); dbgFile << "unicodeLayerName" << unicodeLayerName; } else if (key == "lyid") { } - else if (key == "lfx2") { + else if (key == "lfx2" || key == "lfxs") { + // lfxs is a special variant of layer styles for group layers + KisAslReader reader; layerStyleXml = reader.readLfx2PsdSection(io); } else if (key == "Patt" || key == "Pat2" || key == "Pat3") { KisAslReader reader; QDomDocument pattern = reader.readPsdSectionPattern(io, blockSize); embeddedPatterns << pattern; } else if (key == "Anno") { } else if (key == "clbl") { } else if (key == "infx") { } else if (key == "knko") { } else if (key == "spf") { } else if (key == "lclr") { } else if (key == "fxrp") { } else if (key == "grdm") { } else if (key == "lsct") { quint32 dividerType = GARBAGE_VALUE_MARK; SAFE_READ_EX(io, dividerType); this->sectionDividerType = (psd_section_type)dividerType; dbgFile << "Reading \"lsct\" block:"; dbgFile << ppVar(blockSize); dbgFile << ppVar(dividerType); if (blockSize >= 12) { quint32 lsctSignature = GARBAGE_VALUE_MARK; const quint32 refSignature1 = 0x3842494D; // '8BIM' in little-endian SAFE_READ_SIGNATURE_EX(io, lsctSignature, refSignature1); this->sectionDividerBlendMode = readFixedString(io); dbgFile << ppVar(this->sectionDividerBlendMode); } // Animation if (blockSize >= 14) { /** * "I don't care * I don't care, no... !" (c) */ } } else if (key == "brst") { } else if (key == "SoCo") { } else if (key == "PtFl") { } else if (key == "GdFl") { } else if (key == "vmsk" || key == "vsms") { // If key is "vsms" then we are writing for (Photoshop CS6) and the document will have a "vscg" key } else if (key == "TySh") { } else if (key == "ffxi") { } else if (key == "lnsr") { } else if (key == "shpa") { } else if (key == "shmd") { } else if (key == "lyvr") { } else if (key == "tsly") { } else if (key == "lmgm") { } else if (key == "vmgm") { } else if (key == "plLd") { // Replaced by SoLd in CS3 } else if (key == "linkD" || key == "lnk2" || key == "lnk3") { } else if (key == "phfl") { } else if (key == "blwh") { } else if (key == "CgEd") { } else if (key == "Txt2") { } else if (key == "vibA") { } else if (key == "pths") { } else if (key == "anFX") { } else if (key == "FMsk") { } else if (key == "SoLd") { } else if (key == "vstk") { } else if (key == "vsCg") { } else if (key == "sn2P") { } else if (key == "vogk") { } else if (key == "Mtrn" || key == "Mt16" || key == "Mt32") { // There is no data associated with these keys. } else if (key == "LMsk") { } else if (key == "expA") { } else if (key == "FXid") { } else if (key == "FEid") { } } } bool PsdAdditionalLayerInfoBlock::write(QIODevice */*io*/, KisNodeSP /*node*/) { return true; } bool PsdAdditionalLayerInfoBlock::valid() { return true; } void PsdAdditionalLayerInfoBlock::writeLuniBlockEx(QIODevice* io, const QString &layerName) { KisAslWriterUtils::writeFixedString("8BIM", io); KisAslWriterUtils::writeFixedString("luni", io); KisAslWriterUtils::OffsetStreamPusher layerNameSizeTag(io, 2); KisAslWriterUtils::writeUnicodeString(layerName, io); } void PsdAdditionalLayerInfoBlock::writeLsctBlockEx(QIODevice* io, psd_section_type sectionType, bool isPassThrough, const QString &blendModeKey) { KisAslWriterUtils::writeFixedString("8BIM", io); KisAslWriterUtils::writeFixedString("lsct", io); KisAslWriterUtils::OffsetStreamPusher sectionTypeSizeTag(io, 2); SAFE_WRITE_EX(io, (quint32)sectionType); QString realBlendModeKey = isPassThrough ? QString("pass") : blendModeKey; KisAslWriterUtils::writeFixedString("8BIM", io); KisAslWriterUtils::writeFixedString(realBlendModeKey, io); } -void PsdAdditionalLayerInfoBlock::writeLfx2BlockEx(QIODevice* io, const QDomDocument &stylesXmlDoc) +void PsdAdditionalLayerInfoBlock::writeLfx2BlockEx(QIODevice* io, const QDomDocument &stylesXmlDoc, bool useLfxsLayerStyleFormat) { KisAslWriterUtils::writeFixedString("8BIM", io); - KisAslWriterUtils::writeFixedString("lfx2", io); + // 'lfxs' format is used for Group layers in PS + KisAslWriterUtils::writeFixedString(!useLfxsLayerStyleFormat ? "lfx2" : "lfxs", io); KisAslWriterUtils::OffsetStreamPusher lfx2SizeTag(io, 2); try { KisAslWriter writer; writer.writePsdLfx2SectionEx(io, stylesXmlDoc); } catch (KisAslWriterUtils::ASLWriteException &e) { warnKrita << "WARNING: Couldn't save layer style lfx2 block:" << PREPEND_METHOD(e.what()); // TODO: make this error recoverable! throw e; } } void PsdAdditionalLayerInfoBlock::writePattBlockEx(QIODevice* io, const QDomDocument &patternsXmlDoc) { KisAslWriterUtils::writeFixedString("8BIM", io); KisAslWriterUtils::writeFixedString("Patt", io); KisAslWriterUtils::OffsetStreamPusher pattSizeTag(io, 2); try { KisAslPatternsWriter writer(patternsXmlDoc, io); writer.writePatterns(); } catch (KisAslWriterUtils::ASLWriteException &e) { warnKrita << "WARNING: Couldn't save layer style patterns block:" << PREPEND_METHOD(e.what()); // TODO: make this error recoverable! throw e; } } diff --git a/plugins/impex/psd/psd_additional_layer_info_block.h b/plugins/impex/psd/psd_additional_layer_info_block.h index 72a6374545..4dc020ac26 100644 --- a/plugins/impex/psd/psd_additional_layer_info_block.h +++ b/plugins/impex/psd/psd_additional_layer_info_block.h @@ -1,295 +1,295 @@ /* * Copyright (c) 2014 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PSD_ADDITIONAL_LAYER_INFO_BLOCK_H #define PSD_ADDITIONAL_LAYER_INFO_BLOCK_H #include #include #include #include #include #include #include #include #include #include #include "psd.h" #include "psd_header.h" // additional layer information // LEVELS // Level record struct psd_layer_level_record { quint16 input_floor; // (0...253) quint16 input_ceiling; // (2...255) quint16 output_floor; // 255). Matched to input floor. quint16 output_ceiling; // (0...255) float gamma; // Short integer from 10...999 representing 0.1...9.99. Applied to all image data. }; // Levels settings files are loaded and saved in the Levels dialog. struct psd_layer_levels { psd_layer_level_record record[29]; // 29 sets of level records, each level containing 5 qint8 integers // Photoshop CS (8.0) Additional information // At the end of the Version 2 file is the following information: quint16 extra_level_count; // Count of total level record structures. Subtract the legacy number of level record structures, 29, to determine how many are remaining in the file for reading. psd_layer_level_record *extra_record; // Additianol level records according to count quint8 lookup_table[3][256]; }; // CURVES // The following is the data for each curve specified by count above struct psd_layer_curves_data { quint16 channel_index; // Before each curve is a channel index. quint16 point_count; // Count of points in the curve (qint8 integer from 2...19) quint16 output_value[19]; // All coordinates have range 0 to 255 quint16 input_value[19]; }; // Curves file format struct psd_layer_curves { quint16 curve_count; // Count of curves in the file. psd_layer_curves_data * curve; quint8 lookup_table[3][256]; }; // BRIGHTNESS AND CONTRAST struct psd_layer_brightness_contrast { qint8 brightness; qint8 contrast; qint8 mean_value; // for brightness and contrast qint8 Lab_color; quint8 lookup_table[256]; }; // COLOR BALANCE struct psd_layer_color_balance { qint8 cyan_red[3]; // (-100...100). shadows, midtones, highlights qint8 magenta_green[3]; qint8 yellow_blue[3]; bool preserve_luminosity; quint8 lookup_table[3][256]; }; // HUE/SATURATION // Hue/Saturation settings files are loaded and saved in Photoshop¡¯s Hue/Saturation dialog struct psd_layer_hue_saturation { quint8 hue_or_colorization; // 0 = Use settings for hue-adjustment; 1 = Use settings for colorization. qint8 colorization_hue; // Photoshop 5.0: The actual values are stored for the new version. Hue is - 180...180, Saturation is 0...100, and Lightness is -100...100. qint8 colorization_saturation;// Photoshop 4.0: Three qint8 integers Hue, Saturation, and Lightness from ¨C100...100. qint8 colorization_lightness; // The user interface represents hue as ¨C180...180, saturation as 0...100, and Lightness as -100...1000, as the traditional HSB color wheel, with red = 0. qint8 master_hue; // Master hue, saturation and lightness values. qint8 master_saturation; qint8 master_lightness; qint8 range_values[6][4]; // For RGB and CMYK, those values apply to each of the six hextants in the HSB color wheel: those image pixels nearest to red, yellow, green, cyan, blue, or magenta. These numbers appear in the user interface from ¨C60...60, however the slider will reflect each of the possible 201 values from ¨C100...100. qint8 setting_values[6][3]; // For Lab, the first four of the six values are applied to image pixels in the four Lab color quadrants, yellow, green, blue, and magenta. The other two values are ignored ( = 0). The values appear in the user interface from ¨C90 to 90. quint8 lookup_table[6][360]; }; // SELECTIVE COLOR // Selective Color settings files are loaded and saved in Photoshop¡¯s Selective Color dialog. struct psd_layer_selective_color { quint16 correction_method; // 0 = Apply color correction in relative mode; 1 = Apply color correction in absolute mode. qint8 cyan_correction[10]; // Amount of cyan correction. Short integer from ¨C100...100. qint8 magenta_correction[10]; // Amount of magenta correction. Short integer from ¨C100...100. qint8 yellow_correction[10]; // Amount of yellow correction. Short integer from ¨C100...100. qint8 black_correction[10]; // Amount of black correction. Short integer from ¨C100...100. }; // THRESHOLD struct psd_layer_threshold { quint16 level; // (1...255) } ; // INVERT // no parameter // POSTERIZE struct psd_layer_posterize { quint16 levels; // (2...255) quint8 lookup_table[256]; }; // CHANNEL MIXER struct psd_layer_channel_mixer { bool monochrome; qint8 red_cyan[4]; // RGB or CMYK color plus constant for the mixer settings. 4 * 2 bytes of color with 2 bytes of constant. qint8 green_magenta[4]; // (-200...200) qint8 blue_yellow[4]; qint8 black[4]; qint8 constant[4]; }; // PHOTO FILTER struct psd_layer_photo_filter { qint32 x_color; // 4 bytes each for XYZ color qint32 y_color; qint32 z_color; qint32 density; // (1...100) bool preserve_luminosity; }; #include struct psd_layer_solid_color { quint32 id; QColor fill_color; }; struct psd_layer_gradient_fill { quint32 id; double angle; psd_gradient_style style; qint32 scale; bool reverse; // Is gradient reverse bool dithered; // Is gradient dithered bool align_with_layer; psd_gradient_color gradient_color; }; struct psd_layer_pattern_fill { quint32 id; psd_pattern_info pattern_info; qint32 scale; }; struct psd_layer_type_face { qint8 mark; // Mark value qint32 font_type; // Font type data qint8 font_name[256]; // Pascal string of font name qint8 font_family_name[256]; // Pascal string of font family name qint8 font_style_name[256]; // Pascal string of font style name qint8 script; // Script value qint32 number_axes_vector; // Number of design axes vector to follow qint32 * vector; // Design vector value }; struct psd_layer_type_style { qint8 mark; // Mark value qint8 face_mark; // Face mark value qint32 size; // Size value qint32 tracking; // Tracking value qint32 kerning; // Kerning value qint32 leading; // Leading value qint32 base_shift; // Base shift value bool auto_kern; // Auto kern on/off bool rotate; // Rotate up/down }; struct psd_layer_type_line { qint32 char_count; // Character count value qint8 orientation; // Orientation value qint8 alignment; // Alignment value qint8 actual_char; // Actual character as a double byte character qint8 style; // Style value }; struct psd_layer_type_tool { double transform_info[6]; // 6 * 8 double precision numbers for the transform information qint8 faces_count; // Count of faces psd_layer_type_face * face; qint8 styles_count; // Count of styles psd_layer_type_style * style; qint8 type; // Type value qint32 scaling_factor; // Scaling factor value qint32 character_count; // Character count value qint32 horz_place; // Horizontal placement qint32 vert_place; // Vertical placement qint32 select_start; // Select start value qint32 select_end; // Select end value qint8 lines_count; // Line count psd_layer_type_line * line; QColor color; bool anti_alias; // Anti alias on/off }; /** * @brief The PsdAdditionalLayerInfoBlock class implements the Additional Layer Information block * * See: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_71546 */ class PsdAdditionalLayerInfoBlock { public: PsdAdditionalLayerInfoBlock(const PSDHeader& header); typedef boost::function ExtraLayerInfoBlockHandler; void setExtraLayerInfoBlockHandler(ExtraLayerInfoBlockHandler handler); bool read(QIODevice* io); bool write(QIODevice* io, KisNodeSP node); void writeLuniBlockEx(QIODevice* io, const QString &layerName); void writeLsctBlockEx(QIODevice* io, psd_section_type sectionType, bool isPassThrough, const QString &blendModeKey); - void writeLfx2BlockEx(QIODevice* io, const QDomDocument &stylesXmlDoc); + void writeLfx2BlockEx(QIODevice* io, const QDomDocument &stylesXmlDoc, bool useLfxsLayerStyleFormat); void writePattBlockEx(QIODevice* io, const QDomDocument &patternsXmlDoc); bool valid(); const PSDHeader &m_header; QString error; QStringList keys; // List of all the keys that we've seen QString unicodeLayerName; QDomDocument layerStyleXml; QVector embeddedPatterns; psd_section_type sectionDividerType; QString sectionDividerBlendMode; private: void readImpl(QIODevice* io); private: ExtraLayerInfoBlockHandler m_layerInfoBlockHandler; }; #endif // PSD_ADDITIONAL_LAYER_INFO_BLOCK_H diff --git a/plugins/impex/psd/psd_layer_record.cpp b/plugins/impex/psd/psd_layer_record.cpp index 594d87de9b..314355fad6 100644 --- a/plugins/impex/psd/psd_layer_record.cpp +++ b/plugins/impex/psd/psd_layer_record.cpp @@ -1,759 +1,760 @@ /* * Copyright (c) 2009 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 "psd_layer_record.h" #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #include "psd_utils.h" #include "psd_header.h" #include "compression.h" #include #include #include #include #include #include #include #include "psd_pixel_utils.h" #include // Just for pretty debug messages QString channelIdToChannelType(int channelId, psd_color_mode colormode) { switch(channelId) { case -3: return "Real User Supplied Layer Mask (when both a user mask and a vector mask are present"; case -2: return "User Supplied Layer Mask"; case -1: return "Transparency mask"; case 0: switch(colormode) { case Bitmap: case Indexed: return QString("bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return "gray"; case RGB: case RGB48: return "red"; case Lab: case Lab48: return "L"; case CMYK: case CMYK64: return "cyan"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 1: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return "green"; case Lab: case Lab48: return "a"; case CMYK: case CMYK64: return "Magenta"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 2: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return "blue"; case Lab: case Lab48: return "b"; case CMYK: case CMYK64: return "yellow"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 3: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return QString("alpha: %1").arg(channelId); case Lab: case Lab48: return QString("alpha: %1").arg(channelId); case CMYK: case CMYK64: return "Key"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; default: return QString("unknown: %1").arg(channelId); }; } PSDLayerRecord::PSDLayerRecord(const PSDHeader& header) : top(0) , left(0) , bottom(0) , right(0) , nChannels(0) , opacity(0) , clipping(0) , transparencyProtected(false) , visible(true) , irrelevant(false) , layerName("UNINITIALIZED") , infoBlocks(header) , m_transparencyMaskSizeOffset(0) , m_header(header) { } bool PSDLayerRecord::read(QIODevice* io) { dbgFile << "Going to read layer record. Pos:" << io->pos(); if (!psdread(io, &top) || !psdread(io, &left) || !psdread(io, &bottom) || !psdread(io, &right) || !psdread(io, &nChannels)) { error = "could not read layer record"; return false; } dbgFile << "\ttop" << top << "left" << left << "bottom" << bottom << "right" << right << "number of channels" << nChannels; Q_ASSERT(top <= bottom); Q_ASSERT(left <= right); Q_ASSERT(nChannels > 0); if (nChannels < 1) { error = QString("Not enough channels. Got: %1").arg(nChannels); return false; } if (nChannels > MAX_CHANNELS) { error = QString("Too many channels. Got: %1").arg(nChannels); return false; } for (int i = 0; i < nChannels; ++i) { if (io->atEnd()) { error = "Could not read enough data for channels"; return false; } ChannelInfo* info = new ChannelInfo; if (!psdread(io, &info->channelId)) { error = "could not read channel id"; delete info; return false; } bool r; if (m_header.version == 1) { quint32 channelDataLength; r = psdread(io, &channelDataLength); info->channelDataLength = (quint64)channelDataLength; } else { r = psdread(io, &info->channelDataLength); } if (!r) { error = "Could not read length for channel data"; delete info; return false; } dbgFile << "\tchannel" << i << "id" << channelIdToChannelType(info->channelId, m_header.colormode) << "length" << info->channelDataLength << "start" << info->channelDataStart << "offset" << info->channelOffset << "channelInfoPosition" << info->channelInfoPosition; channelInfoRecords << info; } if (!psd_read_blendmode(io, blendModeKey)) { error = QString("Could not read blend mode key. Got: %1").arg(blendModeKey); return false; } dbgFile << "\tBlend mode" << blendModeKey << "pos" << io->pos(); if (!psdread(io, &opacity)) { error = "Could not read opacity"; return false; } dbgFile << "\tOpacity" << opacity << io->pos(); if (!psdread(io, &clipping)) { error = "Could not read clipping"; return false; } dbgFile << "\tclipping" << clipping << io->pos(); quint8 flags; if (!psdread(io, &flags)) { error = "Could not read flags"; return false; } dbgFile << "\tflags" << flags << io->pos(); transparencyProtected = flags & 1 ? true : false; dbgFile << "\ttransparency protected" << transparencyProtected; visible = flags & 2 ? false : true; dbgFile << "\tvisible" << visible; if (flags & 8) { irrelevant = flags & 16 ? true : false; } else { irrelevant = false; } dbgFile << "\tirrelevant" << irrelevant; dbgFile << "\tfiller at " << io->pos(); quint8 filler; if (!psdread(io, &filler) || filler != 0) { error = "Could not read padding"; return false; } dbgFile << "\tGoing to read extra data length" << io->pos(); quint32 extraDataLength; if (!psdread(io, &extraDataLength) || io->bytesAvailable() < extraDataLength) { error = QString("Could not read extra layer data: %1 at pos %2").arg(extraDataLength).arg(io->pos()); return false; } dbgFile << "\tExtra data length" << extraDataLength; if (extraDataLength > 0) { dbgFile << "Going to read extra data field. Bytes available: " << io->bytesAvailable() << "pos" << io->pos(); quint32 layerMaskLength = 1; // invalid... if (!psdread(io, &layerMaskLength) || io->bytesAvailable() < layerMaskLength || !(layerMaskLength == 0 || layerMaskLength == 20 || layerMaskLength == 36)) { error = QString("Could not read layer mask length: %1").arg(layerMaskLength); return false; } memset(&layerMask, 0, sizeof(LayerMaskData)); if (layerMaskLength == 20 || layerMaskLength == 36) { if (!psdread(io, &layerMask.top) || !psdread(io, &layerMask.left) || !psdread(io, &layerMask.bottom) || !psdread(io, &layerMask.right) || !psdread(io, &layerMask.defaultColor) || !psdread(io, &flags)) { error = "could not read mask record"; return false; } } if (layerMaskLength == 20) { quint16 padding; if (!psdread(io, &padding)) { error = "Could not read layer mask padding"; return false; } } if (layerMaskLength == 36 ) { if (!psdread(io, &flags) || !psdread(io, &layerMask.defaultColor) || !psdread(io, &layerMask.top) || !psdread(io, &layerMask.left) || !psdread(io, &layerMask.bottom) || !psdread(io, &layerMask.top)) { error = "could not read 'real' mask record"; return false; } } layerMask.positionedRelativeToLayer = flags & 1 ? true : false; layerMask.disabled = flags & 2 ? true : false; layerMask.invertLayerMaskWhenBlending = flags & 4 ? true : false; dbgFile << "\tRead layer mask/adjustment layer data. Length of block:" << layerMaskLength << "pos" << io->pos(); // layer blending thingies quint32 blendingDataLength; if (!psdread(io, &blendingDataLength) || io->bytesAvailable() < blendingDataLength) { error = "Could not read extra blending data."; return false; } //dbgFile << "blending block data length" << blendingDataLength << ", pos" << io->pos(); blendingRanges.data = io->read(blendingDataLength); if ((quint32)blendingRanges.data.size() != blendingDataLength) { error = QString("Got %1 bytes for the blending range block, needed %2").arg(blendingRanges.data.size(), blendingDataLength); } /* // XXX: reading this block correctly failed, I have more channel ranges than I'd expected. if (!psdread(io, &blendingRanges.blackValues[0]) || !psdread(io, &blendingRanges.blackValues[1]) || !psdread(io, &blendingRanges.whiteValues[0]) || !psdread(io, &blendingRanges.whiteValues[1]) || !psdread(io, &blendingRanges.compositeGrayBlendDestinationRange)) { error = "Could not read blending black/white values"; return false; } for (int i = 0; i < nChannels; ++i) { quint32 src; quint32 dst; if (!psdread(io, &src) || !psdread(io, &dst)) { error = QString("could not read src/dst range for channel %1").arg(i); return false; } dbgFile << "\tread range " << src << "to" << dst << "for channel" << i; blendingRanges.sourceDestinationRanges << QPair(src, dst); } */ dbgFile << "\tGoing to read layer name at" << io->pos(); quint8 layerNameLength; if (!psdread(io, &layerNameLength)) { error = "Could not read layer name length"; return false; } dbgFile << "\tlayer name length unpadded" << layerNameLength << "pos" << io->pos(); layerNameLength = ((layerNameLength + 1 + 3) & ~0x03) - 1; dbgFile << "\tlayer name length padded" << layerNameLength << "pos" << io->pos(); layerName = io->read(layerNameLength); dbgFile << "\tlayer name" << layerName << io->pos(); if (!infoBlocks.read(io)) { error = infoBlocks.error; return false; } if (infoBlocks.keys.contains("luni") && !infoBlocks.unicodeLayerName.isEmpty()) { layerName = infoBlocks.unicodeLayerName; } } return valid(); } void PSDLayerRecord::write(QIODevice* io, KisPaintDeviceSP layerContentDevice, KisNodeSP onlyTransparencyMask, const QRect &maskRect, psd_section_type sectionType, - const QDomDocument &stylesXmlDoc) + const QDomDocument &stylesXmlDoc, + bool useLfxsLayerStyleFormat) { dbgFile << "writing layer info record" << "at" << io->pos(); m_layerContentDevice = layerContentDevice; m_onlyTransparencyMask = onlyTransparencyMask; m_onlyTransparencyMaskRect = maskRect; dbgFile << "saving layer record for " << layerName << "at pos" << io->pos(); dbgFile << "\ttop" << top << "left" << left << "bottom" << bottom << "right" << right << "number of channels" << nChannels; Q_ASSERT(left <= right); Q_ASSERT(top <= bottom); Q_ASSERT(nChannels > 0); try { const QRect layerRect(left, top, right - left, bottom - top); KisAslWriterUtils::writeRect(layerRect, io); { quint16 realNumberOfChannels = nChannels + bool(m_onlyTransparencyMask); SAFE_WRITE_EX(io, realNumberOfChannels); } Q_FOREACH (ChannelInfo *channel, channelInfoRecords) { SAFE_WRITE_EX(io, (quint16)channel->channelId); channel->channelInfoPosition = io->pos(); // to be filled in when we know how big channel block is const quint32 fakeChannelSize = 0; SAFE_WRITE_EX(io, fakeChannelSize); } if (m_onlyTransparencyMask) { const quint16 userSuppliedMaskChannelId = -2; SAFE_WRITE_EX(io, userSuppliedMaskChannelId); m_transparencyMaskSizeOffset = io->pos(); const quint32 fakeTransparencyMaskSize = 0; SAFE_WRITE_EX(io, fakeTransparencyMaskSize); } // blend mode dbgFile << ppVar(blendModeKey) << ppVar(io->pos()); KisAslWriterUtils::writeFixedString("8BIM", io); KisAslWriterUtils::writeFixedString(blendModeKey, io); SAFE_WRITE_EX(io, opacity); SAFE_WRITE_EX(io, clipping); // unused // visibility and protection quint8 flags = 0; if (transparencyProtected) flags |= 1; if (!visible) flags |= 2; if (irrelevant) { flags |= (1 << 3) | (1 << 4); } SAFE_WRITE_EX(io, flags); { quint8 padding = 0; SAFE_WRITE_EX(io, padding); } { // extra fields with their own length tag KisAslWriterUtils::OffsetStreamPusher extraDataSizeTag(io); if (m_onlyTransparencyMask) { { const quint32 layerMaskDataSize = 20; // support simple case only SAFE_WRITE_EX(io, layerMaskDataSize); } KisAslWriterUtils::writeRect(m_onlyTransparencyMaskRect, io); { // NOTE: in PSD the default color of the mask is stored in 1 byte value! // Even when the mask is actually 16/32 bit! I have no idea how it is // actually treated in this case. KIS_ASSERT_RECOVER_NOOP(m_onlyTransparencyMask->paintDevice()->pixelSize() == 1); const quint8 defaultPixel = *m_onlyTransparencyMask->paintDevice()->defaultPixel().data(); SAFE_WRITE_EX(io, defaultPixel); } { const quint8 maskFlags = 0; // nothing serious SAFE_WRITE_EX(io, maskFlags); const quint16 padding = 0; // 2-byte padding SAFE_WRITE_EX(io, padding); } } else { const quint32 nullLayerMaskDataSize = 0; SAFE_WRITE_EX(io, nullLayerMaskDataSize); } { // blending ranges are not implemented yet const quint32 nullBlendingRangesSize = 0; SAFE_WRITE_EX(io, nullBlendingRangesSize); } // layer name: Pascal string, padded to a multiple of 4 bytes. psdwrite_pascalstring(io, layerName, 4); PsdAdditionalLayerInfoBlock additionalInfoBlock(m_header); // write 'luni' data block additionalInfoBlock.writeLuniBlockEx(io, layerName); // write 'lsct' data block if (sectionType != psd_other) { additionalInfoBlock.writeLsctBlockEx(io, sectionType, isPassThrough, blendModeKey); } // write 'lfx2' data block if (!stylesXmlDoc.isNull()) { - additionalInfoBlock.writeLfx2BlockEx(io, stylesXmlDoc); + additionalInfoBlock.writeLfx2BlockEx(io, stylesXmlDoc, useLfxsLayerStyleFormat); } } } catch (KisAslWriterUtils::ASLWriteException &e) { throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what())); } } KisPaintDeviceSP PSDLayerRecord::convertMaskDeviceIfNeeded(KisPaintDeviceSP dev) { KisPaintDeviceSP result = dev; if (m_header.channelDepth == 16) { result = new KisPaintDevice(*dev); delete result->convertTo(KoColorSpaceRegistry::instance()->alpha16()); } else if (m_header.channelDepth == 32) { result = new KisPaintDevice(*dev); delete result->convertTo(KoColorSpaceRegistry::instance()->alpha32f()); } return result; } void PSDLayerRecord::writeTransparencyMaskPixelData(QIODevice *io) { if (m_onlyTransparencyMask) { KisPaintDeviceSP device = convertMaskDeviceIfNeeded(m_onlyTransparencyMask->paintDevice()); QByteArray buffer(device->pixelSize() * m_onlyTransparencyMaskRect.width() * m_onlyTransparencyMaskRect.height(), 0); device->readBytes((quint8*)buffer.data(), m_onlyTransparencyMaskRect); PsdPixelUtils::writeChannelDataRLE(io, (quint8*)buffer.data(), device->pixelSize(), m_onlyTransparencyMaskRect, m_transparencyMaskSizeOffset, -1, true); } } void PSDLayerRecord::writePixelData(QIODevice *io) { try { writePixelDataImpl(io); } catch (KisAslWriterUtils::ASLWriteException &e) { throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what())); } } void PSDLayerRecord::writePixelDataImpl(QIODevice *io) { dbgFile << "writing pixel data for layer" << layerName << "at" << io->pos(); KisPaintDeviceSP dev = m_layerContentDevice; const QRect rc(left, top, right - left, bottom - top); if (rc.isEmpty()) { dbgFile << "Layer is empty! Writing placeholder information."; for (int i = 0; i < nChannels; i++) { const ChannelInfo *channelInfo = channelInfoRecords[i]; KisAslWriterUtils::OffsetStreamPusher channelBlockSizeExternalTag(io, 0, channelInfo->channelInfoPosition); SAFE_WRITE_EX(io, (quint16)Compression::Uncompressed); } writeTransparencyMaskPixelData(io); return; } // now write all the channels in display order dbgFile << "layer" << layerName; const int channelSize = m_header.channelDepth / 8; const psd_color_mode colorMode = m_header.colormode; QVector writingInfoList; Q_FOREACH (const ChannelInfo *channelInfo, channelInfoRecords) { writingInfoList << PsdPixelUtils::ChannelWritingInfo(channelInfo->channelId, channelInfo->channelInfoPosition); } PsdPixelUtils::writePixelDataCommon(io, dev, rc, colorMode, channelSize, true, true, writingInfoList); writeTransparencyMaskPixelData(io); } bool PSDLayerRecord::valid() { // XXX: check validity! return true; } bool PSDLayerRecord::readPixelData(QIODevice *io, KisPaintDeviceSP device) { dbgFile << "Reading pixel data for layer" << layerName << "pos" << io->pos(); const int channelSize = m_header.channelDepth / 8; const QRect layerRect = QRect(left, top, right - left, bottom - top); try { PsdPixelUtils::readChannels(io, device, m_header.colormode, channelSize, layerRect, channelInfoRecords); } catch (KisAslReaderUtils::ASLParseException &e) { device->clear(); error = e.what(); return false; } return true; } QRect PSDLayerRecord::channelRect(ChannelInfo *channel) const { QRect result; if (channel->channelId < -1) { result = QRect(layerMask.left, layerMask.top, layerMask.right - layerMask.left, layerMask.bottom - layerMask.top); } else { result = QRect(left, top, right - left, bottom - top); } return result; } bool PSDLayerRecord::readMask(QIODevice *io, KisPaintDeviceSP dev, ChannelInfo *channelInfo) { KIS_ASSERT_RECOVER(channelInfo->channelId < -1) { return false; } dbgFile << "Going to read" << channelIdToChannelType(channelInfo->channelId, m_header.colormode) << "mask"; QRect maskRect = channelRect(channelInfo); if (maskRect.isEmpty()) { dbgFile << "Empty Channel"; return true; } // the device must be a pixel selection KIS_ASSERT_RECOVER(dev->pixelSize() == 1) { return false; } dev->setDefaultPixel(KoColor(&layerMask.defaultColor, dev->colorSpace())); const int pixelSize = m_header.channelDepth == 16 ? 2 : m_header.channelDepth == 32 ? 4 : 1; QVector infoRecords; infoRecords << channelInfo; PsdPixelUtils::readAlphaMaskChannels(io, dev, pixelSize, maskRect, infoRecords); return true; } QDebug operator<<(QDebug dbg, const PSDLayerRecord &layer) { #ifndef NODEBUG dbg.nospace() << "valid: " << const_cast(&layer)->valid(); dbg.nospace() << ", name: " << layer.layerName; dbg.nospace() << ", top: " << layer.top; dbg.nospace() << ", left:" << layer.left; dbg.nospace() << ", bottom: " << layer.bottom; dbg.nospace() << ", right: " << layer.right; dbg.nospace() << ", number of channels: " << layer.nChannels; dbg.nospace() << ", blendModeKey: " << layer.blendModeKey; dbg.nospace() << ", opacity: " << layer.opacity; dbg.nospace() << ", clipping: " << layer.clipping; dbg.nospace() << ", transparency protected: " << layer.transparencyProtected; dbg.nospace() << ", visible: " << layer.visible; dbg.nospace() << ", irrelevant: " << layer.irrelevant << "\n"; Q_FOREACH (const ChannelInfo* channel, layer.channelInfoRecords) { dbg.space() << channel; } #endif return dbg.nospace(); } QDebug operator<<(QDebug dbg, const ChannelInfo &channel) { #ifndef NODEBUG dbg.nospace() << "\tChannel type" << channel.channelId << "size: " << channel.channelDataLength << "compression type" << channel.compressionType << "\n"; #endif return dbg.nospace(); } diff --git a/plugins/impex/psd/psd_layer_record.h b/plugins/impex/psd/psd_layer_record.h index 002411986b..3ccb03864c 100644 --- a/plugins/impex/psd/psd_layer_record.h +++ b/plugins/impex/psd/psd_layer_record.h @@ -1,178 +1,178 @@ /* * Copyright (c) 2009 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 PSD_LAYER_RECORD_H #define PSD_LAYER_RECORD_H #include #include #include #include #include #include #include #include "psd.h" #include "psd_header.h" #include "compression.h" #include "psd_additional_layer_info_block.h" #include class QIODevice; enum psd_layer_type { psd_layer_type_normal, psd_layer_type_hidden, psd_layer_type_folder, psd_layer_type_solid_color, psd_layer_type_gradient_fill, psd_layer_type_pattern_fill, psd_layer_type_levels, psd_layer_type_curves, psd_layer_type_brightness_contrast, psd_layer_type_color_balance, psd_layer_type_hue_saturation, psd_layer_type_selective_color, psd_layer_type_threshold, psd_layer_type_invert, psd_layer_type_posterize, psd_layer_type_channel_mixer, psd_layer_type_gradient_map, psd_layer_type_photo_filter, }; struct ChannelInfo { ChannelInfo() : channelId(0) , compressionType(Compression::Unknown) , channelDataStart(0) , channelDataLength(0) , channelOffset(0) , channelInfoPosition(0) {} qint16 channelId; // 0 red, 1 green, 2 blue, -1 transparency, -2 user-supplied layer mask Compression::CompressionType compressionType; quint64 channelDataStart; quint64 channelDataLength; QVector rleRowLengths; int channelOffset; // where the channel data starts int channelInfoPosition; // where the channelinfo record is saved in the file }; class PSDLayerRecord { public: PSDLayerRecord(const PSDHeader &header); ~PSDLayerRecord() { qDeleteAll(channelInfoRecords); } QRect channelRect(ChannelInfo *channel) const; bool read(QIODevice* io); bool readPixelData(QIODevice* io, KisPaintDeviceSP device); bool readMask(QIODevice* io, KisPaintDeviceSP dev, ChannelInfo *channel); - void write(QIODevice* io, KisPaintDeviceSP layerContentDevice, KisNodeSP onlyTransparencyMask, const QRect &maskRect, psd_section_type sectionType, const QDomDocument &stylesXmlDoc); + void write(QIODevice* io, KisPaintDeviceSP layerContentDevice, KisNodeSP onlyTransparencyMask, const QRect &maskRect, psd_section_type sectionType, const QDomDocument &stylesXmlDoc, bool useLfxsLayerStyleFormat); void writePixelData(QIODevice* io); bool valid(); QString error; qint32 top; qint32 left; qint32 bottom; qint32 right; quint16 nChannels; QVector channelInfoRecords; QString blendModeKey; bool isPassThrough; quint8 opacity; quint8 clipping; bool transparencyProtected; bool visible; bool irrelevant; struct LayerMaskData { qint32 top; qint32 left; qint32 bottom; qint32 right; quint8 defaultColor; // 0 or 255 bool positionedRelativeToLayer; bool disabled; bool invertLayerMaskWhenBlending; quint8 userMaskDensity; double userMaskFeather; quint8 vectorMaskDensity; double vectorMaskFeather; }; LayerMaskData layerMask; struct LayerBlendingRanges { QByteArray data; quint8 blackValues[2]; quint8 whiteValues[2]; quint32 compositeGrayBlendDestinationRange; QVector > sourceDestinationRanges; }; LayerBlendingRanges blendingRanges; QString layerName; // pascal, not unicode! PsdAdditionalLayerInfoBlock infoBlocks; private: void writeTransparencyMaskPixelData(QIODevice *io); void writePixelDataImpl(QIODevice *io); KisPaintDeviceSP convertMaskDeviceIfNeeded(KisPaintDeviceSP dev); private: KisPaintDeviceSP m_layerContentDevice; KisNodeSP m_onlyTransparencyMask; QRect m_onlyTransparencyMaskRect; qint64 m_transparencyMaskSizeOffset; const PSDHeader m_header; }; QDebug operator<<(QDebug dbg, const PSDLayerRecord& layer); QDebug operator<<(QDebug dbg, const ChannelInfo& layer); #endif // PSD_LAYER_RECORD_H diff --git a/plugins/impex/psd/psd_layer_section.cpp b/plugins/impex/psd/psd_layer_section.cpp index a9efb0ac23..2c064a5bce 100644 --- a/plugins/impex/psd/psd_layer_section.cpp +++ b/plugins/impex/psd/psd_layer_section.cpp @@ -1,588 +1,589 @@ /* * Copyright (c) 2009 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 "psd_layer_section.h" #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include "psd_header.h" #include "psd_utils.h" #include "compression.h" #include #include #include #include PSDLayerMaskSection::PSDLayerMaskSection(const PSDHeader& header) : globalInfoSection(header), m_header(header) { hasTransparency = false; layerMaskBlockSize = 0; nLayers = 0; } PSDLayerMaskSection::~PSDLayerMaskSection() { qDeleteAll(layers); } bool PSDLayerMaskSection::read(QIODevice* io) { bool retval = true; // be optimistic! <:-) try { retval = readImpl(io); } catch (KisAslReaderUtils::ASLParseException &e) { warnKrita << "WARNING: PSD (emb. pattern):" << e.what(); retval = false; } return retval; } bool PSDLayerMaskSection::readLayerInfoImpl(QIODevice* io) { quint32 layerInfoSectionSize = 0; SAFE_READ_EX(io, layerInfoSectionSize); if (layerInfoSectionSize & 0x1) { warnKrita << "WARNING: layerInfoSectionSize is NOT even! Fixing..."; layerInfoSectionSize++; } { SETUP_OFFSET_VERIFIER(layerInfoSectionTag, io, layerInfoSectionSize, 0); dbgFile << "Layer info block size" << layerInfoSectionSize; if (layerInfoSectionSize > 0 ) { if (!psdread(io, &nLayers) || nLayers == 0) { error = QString("Could not read read number of layers or no layers in image. %1").arg(nLayers); return false; } hasTransparency = nLayers < 0; // first alpha channel is the alpha channel of the projection. nLayers = qAbs(nLayers); dbgFile << "Number of layers:" << nLayers; dbgFile << "Has separate projection transparency:" << hasTransparency; for (int i = 0; i < nLayers; ++i) { dbgFile << "Going to read layer" << i << "pos" << io->pos(); dbgFile << "== Enter PSDLayerRecord"; PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header); if (!layerRecord->read(io)) { error = QString("Could not load layer %1: %2").arg(i).arg(layerRecord->error); return false; } dbgFile << "== Leave PSDLayerRecord"; dbgFile << "Finished reading layer" << i << layerRecord->layerName << "blending mode" << layerRecord->blendModeKey << io->pos() << "Number of channels:" << layerRecord->channelInfoRecords.size(); layers << layerRecord; } } // get the positions for the channels belonging to each layer for (int i = 0; i < nLayers; ++i) { dbgFile << "Going to seek channel positions for layer" << i << "pos" << io->pos(); if (i > layers.size()) { error = QString("Expected layer %1, but only have %2 layers").arg(i).arg(layers.size()); return false; } PSDLayerRecord *layerRecord = layers.at(i); for (int j = 0; j < layerRecord->nChannels; ++j) { // save the current location so we can jump beyond this block later on. quint64 channelStartPos = io->pos(); dbgFile << "\tReading channel image data for channel" << j << "from pos" << io->pos(); KIS_ASSERT_RECOVER(j < layerRecord->channelInfoRecords.size()) { return false; } ChannelInfo* channelInfo = layerRecord->channelInfoRecords.at(j); quint16 compressionType; if (!psdread(io, &compressionType)) { error = "Could not read compression type for channel"; return false; } channelInfo->compressionType = (Compression::CompressionType)compressionType; dbgFile << "\t\tChannel" << j << "has compression type" << compressionType; QRect channelRect = layerRecord->channelRect(channelInfo); // read the rle row lengths; if (channelInfo->compressionType == Compression::RLE) { for(qint64 row = 0; row < channelRect.height(); ++row) { //dbgFile << "Reading the RLE bytecount position of row" << row << "at pos" << io->pos(); quint32 byteCount; if (m_header.version == 1) { quint16 _byteCount; if (!psdread(io, &_byteCount)) { error = QString("Could not read byteCount for rle-encoded channel"); return 0; } byteCount = _byteCount; } else { if (!psdread(io, &byteCount)) { error = QString("Could not read byteCount for rle-encoded channel"); return 0; } } ////dbgFile << "rle byte count" << byteCount; channelInfo->rleRowLengths << byteCount; } } // we're beyond all the length bytes, rle bytes and whatever, this is the // location of the real pixel data channelInfo->channelDataStart = io->pos(); dbgFile << "\t\tstart" << channelStartPos << "data start" << channelInfo->channelDataStart << "data length" << channelInfo->channelDataLength << "pos" << io->pos(); // make sure we are at the start of the next channel data block io->seek(channelStartPos + channelInfo->channelDataLength); // this is the length of the actual channel data bytes channelInfo->channelDataLength = channelInfo->channelDataLength - (channelInfo->channelDataStart - channelStartPos); dbgFile << "\t\tchannel record" << j << "for layer" << i << "with id" << channelInfo->channelId << "starting position" << channelInfo->channelDataStart << "with length" << channelInfo->channelDataLength << "and has compression type" << channelInfo->compressionType; } } } return true; } bool PSDLayerMaskSection::readImpl(QIODevice* io) { dbgFile << "reading layer section. Pos:" << io->pos() << "bytes left:" << io->bytesAvailable(); layerMaskBlockSize = 0; if (m_header.version == 1) { quint32 _layerMaskBlockSize = 0; if (!psdread(io, &_layerMaskBlockSize) || _layerMaskBlockSize > (quint64)io->bytesAvailable()) { error = QString("Could not read layer + mask block size. Got %1. Bytes left %2") .arg(_layerMaskBlockSize).arg(io->bytesAvailable()); return false; } layerMaskBlockSize = _layerMaskBlockSize; } else if (m_header.version == 2) { if (!psdread(io, &layerMaskBlockSize) || layerMaskBlockSize > (quint64)io->bytesAvailable()) { error = QString("Could not read layer + mask block size. Got %1. Bytes left %2") .arg(layerMaskBlockSize).arg(io->bytesAvailable()); return false; } } quint64 start = io->pos(); dbgFile << "layer + mask section size" << layerMaskBlockSize; if (layerMaskBlockSize == 0) { dbgFile << "No layer + mask info, so no layers, only a background layer"; return true; } if (!readLayerInfoImpl(io)) { return false; } quint32 globalMaskBlockLength; if (!psdread(io, &globalMaskBlockLength)) { error = "Could not read global mask info block"; return false; } if (globalMaskBlockLength > 0) { if (!psdread(io, &globalLayerMaskInfo.overlayColorSpace)) { error = "Could not read global mask info overlay colorspace"; return false; } for (int i = 0; i < 4; ++i) { if (!psdread(io, &globalLayerMaskInfo.colorComponents[i])) { error = QString("Could not read mask info visualizaion color component %1").arg(i); return false; } } if (!psdread(io, &globalLayerMaskInfo.opacity)) { error = "Could not read global mask info visualization opacity"; return false; } if (!psdread(io, &globalLayerMaskInfo.kind)) { error = "Could not read global mask info visualization type"; return false; } } // global additional sections /** * Newer versions of PSD have layers info block wrapped into * 'Lr16' or 'Lr32' additional section, while the main block is * absent. * * Here we pass the callback which should be used when such * additional section is recognized. */ globalInfoSection.setExtraLayerInfoBlockHandler(std::bind(&PSDLayerMaskSection::readLayerInfoImpl, this, std::placeholders::_1)); globalInfoSection.read(io); /* put us after this section so reading the next section will work even if we mess up */ io->seek(start + layerMaskBlockSize); return true; } struct FlattenedNode { FlattenedNode() : type(RASTER_LAYER) {} KisNodeSP node; enum Type { RASTER_LAYER, FOLDER_OPEN, FOLDER_CLOSED, SECTION_DIVIDER }; Type type; }; void addBackgroundIfNeeded(KisNodeSP root, QList &nodes) { KisGroupLayer *group = dynamic_cast(root.data()); if (!group) return; KoColor projectionColor = group->defaultProjectionColor(); if (projectionColor.opacityU8() == OPACITY_TRANSPARENT_U8) return; KisPaintLayerSP layer = new KisPaintLayer(group->image(), i18nc("Automatically created layer name when saving into PSD", "Background"), OPACITY_OPAQUE_U8); layer->paintDevice()->setDefaultPixel(projectionColor); { FlattenedNode item; item.node = layer; item.type = FlattenedNode::RASTER_LAYER; nodes << item; } } void flattenNodes(KisNodeSP node, QList &nodes) { KisNodeSP child = node->firstChild(); while (child) { bool isGroupLayer = child->inherits("KisGroupLayer"); bool isRasterLayer = child->inherits("KisPaintLayer") || child->inherits("KisShapeLayer"); if (isGroupLayer) { { FlattenedNode item; item.node = child; item.type = FlattenedNode::SECTION_DIVIDER; nodes << item; } flattenNodes(child, nodes); { FlattenedNode item; item.node = child; item.type = FlattenedNode::FOLDER_OPEN; nodes << item; } } else if (isRasterLayer) { FlattenedNode item; item.node = child; item.type = FlattenedNode::RASTER_LAYER; nodes << item; } child = child->nextSibling(); } } KisNodeSP findOnlyTransparencyMask(KisNodeSP node, FlattenedNode::Type type) { if (type != FlattenedNode::FOLDER_OPEN && type != FlattenedNode::FOLDER_CLOSED && type != FlattenedNode::RASTER_LAYER) { return 0; } KisLayer *layer = qobject_cast(node.data()); QList masks = layer->effectMasks(); if (masks.size() != 1) return 0; KisEffectMaskSP onlyMask = masks.first(); return onlyMask->inherits("KisTransparencyMask") ? onlyMask : 0; } QDomDocument fetchLayerStyleXmlData(KisNodeSP node) { const KisLayer *layer = qobject_cast(node.data()); KisPSDLayerStyleSP layerStyle = layer->layerStyle(); if (!layerStyle) return QDomDocument(); KisAslLayerStyleSerializer serializer; serializer.setStyles(QVector() << layerStyle); return serializer.formPsdXmlDocument(); } inline QDomNode findNodeByKey(const QString &key, QDomNode parent) { return KisDomUtils::findElementByAttibute(parent, "node", "key", key); } void mergePatternsXMLSection(const QDomDocument &src, QDomDocument &dst) { QDomNode srcPatternsNode = findNodeByKey("Patterns", src.documentElement()); QDomNode dstPatternsNode = findNodeByKey("Patterns", dst.documentElement()); if (srcPatternsNode.isNull()) return; if (dstPatternsNode.isNull()) { dst = src; return; } KIS_ASSERT_RECOVER_RETURN(!srcPatternsNode.isNull()); KIS_ASSERT_RECOVER_RETURN(!dstPatternsNode.isNull()); QDomNode node = srcPatternsNode.firstChild(); while(!node.isNull()) { QDomNode importedNode = dst.importNode(node, true); KIS_ASSERT_RECOVER_RETURN(!importedNode.isNull()); dstPatternsNode.appendChild(importedNode); node = node.nextSibling(); } } bool PSDLayerMaskSection::write(QIODevice* io, KisNodeSP rootLayer) { bool retval = true; try { writeImpl(io, rootLayer); } catch (KisAslWriterUtils::ASLWriteException &e) { error = PREPEND_METHOD(e.what()); retval = false; } return retval; } void PSDLayerMaskSection::writeImpl(QIODevice* io, KisNodeSP rootLayer) { dbgFile << "Writing layer layer section"; // Build the whole layer structure QList nodes; addBackgroundIfNeeded(rootLayer, nodes); flattenNodes(rootLayer, nodes); if (nodes.isEmpty()) { throw KisAslWriterUtils::ASLWriteException("Could not find paint layers to save"); } { KisAslWriterUtils::OffsetStreamPusher layerAndMaskSectionSizeTag(io, 2); QDomDocument mergedPatternsXmlDoc; { KisAslWriterUtils::OffsetStreamPusher layerInfoSizeTag(io, 4); { // number of layers (negative, because krita always has alpha) const qint16 layersSize = -nodes.size(); SAFE_WRITE_EX(io, layersSize); dbgFile << "Number of layers" << layersSize << "at" << io->pos(); } // Layer records section Q_FOREACH (const FlattenedNode &item, nodes) { KisNodeSP node = item.node; PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header); layers.append(layerRecord); KisNodeSP onlyTransparencyMask = findOnlyTransparencyMask(node, item.type); const QRect maskRect = onlyTransparencyMask ? onlyTransparencyMask->paintDevice()->exactBounds() : QRect(); const bool nodeVisible = node->visible(); const KoColorSpace *colorSpace = node->colorSpace(); const quint8 nodeOpacity = node->opacity(); const quint8 nodeClipping = 0; const KisPaintLayer *paintLayer = qobject_cast(node.data()); const bool alphaLocked = (paintLayer && paintLayer->alphaLocked()); const QString nodeCompositeOp = node->compositeOpId(); const KisGroupLayer *groupLayer = qobject_cast(node.data()); const bool nodeIsPassThrough = groupLayer && groupLayer->passThroughMode(); QDomDocument stylesXmlDoc = fetchLayerStyleXmlData(node); if (mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) { mergedPatternsXmlDoc = stylesXmlDoc; } else if (!mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) { mergePatternsXMLSection(stylesXmlDoc, mergedPatternsXmlDoc); } bool nodeIrrelevant = false; QString nodeName; KisPaintDeviceSP layerContentDevice; psd_section_type sectionType; if (item.type == FlattenedNode::RASTER_LAYER) { nodeIrrelevant = false; nodeName = node->name(); layerContentDevice = onlyTransparencyMask ? node->original() : node->projection(); sectionType = psd_other; } else { nodeIrrelevant = true; nodeName = item.type == FlattenedNode::SECTION_DIVIDER ? QString("") : node->name(); layerContentDevice = 0; sectionType = item.type == FlattenedNode::SECTION_DIVIDER ? psd_bounding_divider : item.type == FlattenedNode::FOLDER_OPEN ? psd_open_folder : psd_closed_folder; } // === no access to node anymore QRect layerRect; if (layerContentDevice) { QRect rc = layerContentDevice->exactBounds(); rc = rc.normalized(); // keep to the max of photoshop's capabilities if (rc.width() > 30000) rc.setWidth(30000); if (rc.height() > 30000) rc.setHeight(30000); layerRect = rc; } layerRecord->top = layerRect.y(); layerRecord->left = layerRect.x(); layerRecord->bottom = layerRect.y() + layerRect.height(); layerRecord->right = layerRect.x() + layerRect.width(); // colors + alpha channel // note: transparency mask not included layerRecord->nChannels = colorSpace->colorChannelCount() + 1; ChannelInfo *info = new ChannelInfo; info->channelId = -1; // For the alpha channel, which we always have in Krita, and should be saved first in layerRecord->channelInfoRecords << info; // the rest is in display order: rgb, cmyk, lab... for (int i = 0; i < (int)colorSpace->colorChannelCount(); ++i) { info = new ChannelInfo; info->channelId = i; // 0 for red, 1 = green, etc layerRecord->channelInfoRecords << info; } layerRecord->blendModeKey = composite_op_to_psd_blendmode(nodeCompositeOp); layerRecord->isPassThrough = nodeIsPassThrough; layerRecord->opacity = nodeOpacity; layerRecord->clipping = nodeClipping; layerRecord->transparencyProtected = alphaLocked; layerRecord->visible = nodeVisible; layerRecord->irrelevant = nodeIrrelevant; layerRecord->layerName = nodeName; layerRecord->write(io, layerContentDevice, onlyTransparencyMask, maskRect, sectionType, - stylesXmlDoc); + stylesXmlDoc, + node->inherits("KisGroupLayer")); } dbgFile << "start writing layer pixel data" << io->pos(); // Now save the pixel data Q_FOREACH (PSDLayerRecord *layerRecord, layers) { layerRecord->writePixelData(io); } } { // write the global layer mask info -- which is empty const quint32 globalMaskSize = 0; SAFE_WRITE_EX(io, globalMaskSize); } { PsdAdditionalLayerInfoBlock globalInfoSection(m_header); globalInfoSection.writePattBlockEx(io, mergedPatternsXmlDoc); } } } diff --git a/plugins/impex/psd/psd_loader.cpp b/plugins/impex/psd/psd_loader.cpp index e4ef769d78..981a7f6971 100644 --- a/plugins/impex/psd/psd_loader.cpp +++ b/plugins/impex/psd/psd_loader.cpp @@ -1,373 +1,379 @@ /* * Copyright (c) 2009 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 "psd_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resource_server_provider.h" #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" PSDLoader::PSDLoader(KisDocument *doc) : m_image(0) , m_doc(doc) , m_stop(false) { } PSDLoader::~PSDLoader() { } KisImageBuilder_Result PSDLoader::decode(QIODevice *io) { // open the file dbgFile << "pos:" << io->pos(); PSDHeader header; if (!header.read( io)) { dbgFile << "failed reading header: " << header.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << header; dbgFile << "Read header. pos:" << io->pos(); PSDColorModeBlock colorModeBlock(header.colormode); if (!colorModeBlock.read(io)) { dbgFile << "failed reading colormode block: " << colorModeBlock.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read color mode block. pos:" << io->pos(); PSDImageResourceSection resourceSection; if (!resourceSection.read(io)) { dbgFile << "failed image reading resource section: " << resourceSection.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read image resource section. pos:" << io->pos(); PSDLayerMaskSection layerSection(header); if (!layerSection.read(io)) { dbgFile << "failed reading layer/mask section: " << layerSection.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << io->pos(); // Done reading, except possibly for the image data block, which is only relevant if there // are no layers. // Get the right colorspace QPair colorSpaceId = psd_colormode_to_colormodelid(header.colormode, header.channelDepth); if (colorSpaceId.first.isNull()) { dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth; return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Get the icc profile from the image resource section const KoColorProfile* profile = 0; if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) { ICC_PROFILE_1039 *iccProfileData = dynamic_cast(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource); if (iccProfileData ) { profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, iccProfileData->icc); dbgFile << "Loaded ICC profile" << profile->name(); delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE); } } // Create the colorspace const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); if (!cs) { return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Creating the KisImage QString name = dynamic_cast(io) ? dynamic_cast(io)->fileName() : "Imported"; m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, name); Q_CHECK_PTR(m_image); m_image->lock(); // set the correct resolution if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) { RESN_INFO_1005 *resInfo = dynamic_cast(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource); if (resInfo) { // check resolution size is not zero if (resInfo->hRes * resInfo->vRes > 0) m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes)); // let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it. delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO); } } // Preserve all the annotations Q_FOREACH (PSDResourceBlock *resourceBlock, resourceSection.resources.values()) { m_image->addAnnotation(resourceBlock); } // Preserve the duotone colormode block for saving back to psd if (header.colormode == DuoTone) { KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock", i18n("Duotone Colormode Block"), colorModeBlock.data); m_image->addAnnotation(annotation); } // Read the projection into our single layer. Since we only read the projection when // we have just one layer, we don't need to later on apply the alpha channel of the // first layer to the projection if the number of layers is negative/ // See http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000. if (layerSection.nLayers == 0) { dbgFile << "Position" << io->pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'"; KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8); PSDImageData imageData(&header); imageData.read(io, layer->paintDevice()); m_image->addNode(layer, m_image->rootLayer()); // Only one layer, the background layer, so we're done. m_image->unlock(); return KisImageBuilder_RESULT_OK; } // More than one layer, so now construct the Krita image from the info we read. QStack groupStack; groupStack.push(m_image->rootLayer()); /** * PSD has a weird "optimization": if a group layer has only one * child layer, it omits it's 'psd_bounding_divider' section. So * fi you ever see an unbalanced layers group in PSD, most * probably, it is just a single layered group. */ KisNodeSP lastAddedLayer; typedef QPair LayerStyleMapping; QVector allStylesXml; // read the channels for the various layers for(int i = 0; i < layerSection.nLayers; ++i) { PSDLayerRecord* layerRecord = layerSection.layers.at(i); dbgFile << "Going to read channels for layer" << i << layerRecord->layerName; KisLayerSP newLayer; if (layerRecord->infoBlocks.keys.contains("lsct") && layerRecord->infoBlocks.sectionDividerType != psd_other) { if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) { KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); groupStack.push(groupLayer); newLayer = groupLayer; } else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder || layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) && (groupStack.size() > 1 || (lastAddedLayer && !groupStack.isEmpty()))) { KisGroupLayerSP groupLayer; if (groupStack.size() <= 1) { groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); m_image->moveNode(lastAddedLayer, groupLayer, KisNodeSP()); } else { groupLayer = groupStack.pop(); } + const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; + + if (!styleXml.isNull()) { + allStylesXml << LayerStyleMapping(styleXml, groupLayer); + } + groupLayer->setName(layerRecord->layerName); groupLayer->setVisible(layerRecord->visible); QString compositeOp = psd_blendmode_to_composite_op(layerRecord->infoBlocks.sectionDividerBlendMode); // Krita doesn't support pass-through blend // mode. Instead it is just a property of a goupr // layer, so flip it if (compositeOp == COMPOSITE_PASS_THROUGH) { compositeOp = COMPOSITE_OVER; groupLayer->setPassThroughMode(true); } groupLayer->setCompositeOpId(compositeOp); newLayer = groupLayer; } else { /** * In some files saved by PS CS6 the group layer sections seem * to be unbalanced. I don't know why it happens because the * reporter didn't provide us an example file. So here we just * check if the new layer was created, and if not, skip the * initialization of masks. * * See bug: 357559 */ warnKrita << "WARNING: Provided PSD has unbalanced group " << "layer markers. Some masks and/or layers can " << "be lost while loading this file. Please " << "report a bug to Krita developes and attach " << "this file to the bugreport\n" << " " << ppVar(layerRecord->layerName) << "\n" << " " << ppVar(layerRecord->infoBlocks.sectionDividerType) << "\n" << " " << ppVar(groupStack.size()); continue; } } else { KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity); layer->setCompositeOpId(psd_blendmode_to_composite_op(layerRecord->blendModeKey)); const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, layer); } if (!layerRecord->readPixelData(io, layer->paintDevice())) { dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error; return KisImageBuilder_RESULT_FAILURE; } if (!groupStack.isEmpty()) { m_image->addNode(layer, groupStack.top()); } else { m_image->addNode(layer, m_image->root()); } layer->setVisible(layerRecord->visible); newLayer = layer; } Q_FOREACH (ChannelInfo *channelInfo, layerRecord->channelInfoRecords) { if (channelInfo->channelId < -1) { KisTransparencyMaskSP mask = new KisTransparencyMask(); mask->setName(i18n("Transparency Mask")); mask->initSelection(newLayer); if (!layerRecord->readMask(io, mask->paintDevice(), channelInfo)) { dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; } m_image->addNode(mask, newLayer); } } lastAddedLayer = newLayer; } const QVector &embeddedPatterns = layerSection.globalInfoSection.embeddedPatterns; KisAslLayerStyleSerializer serializer; if (!embeddedPatterns.isEmpty()) { Q_FOREACH (const QDomDocument &doc, embeddedPatterns) { serializer.registerPSDPattern(doc); } } QVector allStylesForServer; if (!allStylesXml.isEmpty()) { Q_FOREACH (const LayerStyleMapping &mapping, allStylesXml) { serializer.readFromPSDXML(mapping.first); if (serializer.styles().size() == 1) { KisPSDLayerStyleSP layerStyle = serializer.styles().first(); KisLayerSP layer = mapping.second; layerStyle->setName(layer->name()); allStylesForServer << layerStyle; layer->setLayerStyle(layerStyle->clone()); } else { warnKrita << "WARNING: Couldn't read layer style!" << ppVar(serializer.styles()); } } } if (!allStylesForServer.isEmpty()) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded PSD Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_image->objectName())); KIS_SAFE_ASSERT_RECOVER_NOOP(!collection->valid()); collection->setLayerStyles(allStylesForServer); KIS_SAFE_ASSERT_RECOVER_NOOP(collection->valid()); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); } m_image->unlock(); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result PSDLoader::buildImage(QIODevice *io) { return decode(io); } KisImageSP PSDLoader::image() { return m_image; } void PSDLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/spriter/CMakeLists.txt b/plugins/impex/spriter/CMakeLists.txt index 40f20764ef..ea7eb1fee7 100644 --- a/plugins/impex/spriter/CMakeLists.txt +++ b/plugins/impex/spriter/CMakeLists.txt @@ -1,17 +1,13 @@ -include_directories(SYSTEM - ${Boost_INCLUDE_DIRS} -) - # export set(kritaspriterexport_SOURCES kis_spriter_export.cpp ) add_library(kritaspriterexport MODULE ${kritaspriterexport_SOURCES}) target_link_libraries(kritaspriterexport kritaui kritaimpex) install(TARGETS kritaspriterexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_spriter.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/video/CMakeLists.txt b/plugins/impex/video/CMakeLists.txt index be546b37b1..8984def8a1 100644 --- a/plugins/impex/video/CMakeLists.txt +++ b/plugins/impex/video/CMakeLists.txt @@ -1,21 +1,17 @@ -include_directories(${Boost_INCLUDE_DIRS}) - # export -include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) - set(kritavideoexport_SOURCES kis_video_export.cpp video_saver.cpp video_export_options_dialog.cpp ) ki18n_wrap_ui(kritavideoexport_SOURCES video_export_options_dialog.ui ) add_library(kritavideoexport MODULE ${kritavideoexport_SOURCES}) generate_export_header(kritavideoexport BASE_NAME kritavideoexport) target_link_libraries(kritavideoexport kritaui) install(TARGETS kritavideoexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/paintops/hairy/bristle.cpp b/plugins/paintops/hairy/bristle.cpp index be41732978..60fa20c97a 100644 --- a/plugins/paintops/hairy/bristle.cpp +++ b/plugins/paintops/hairy/bristle.cpp @@ -1,83 +1,70 @@ /* * Copyright (c) 2008 Lukas Tvrdy * * 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 "bristle.h" -Bristle::Bristle() -{ - init(0, 0, 0); -} - Bristle::Bristle(float x, float y, float length) -{ - init(x, y, length); -} - -void Bristle::init(float x, float y, float length) -{ - m_x = x; - m_y = y; - m_prevX = x; - m_prevY = y; - m_length = length; - m_counter = 0; - m_enabled = true; -} + : m_x(x) + , m_y(y) + , m_prevX(x) + , m_prevY(y) + , m_length(length) +{} Bristle::~Bristle() { } void Bristle::setLength(float length) { m_length = length; } void Bristle::addInk(float value) { m_inkAmount = m_inkAmount + value; } void Bristle::removeInk(float value) { m_inkAmount = m_inkAmount - value; } void Bristle::setInkAmount(float inkAmount) { if (inkAmount > 1.0f) { inkAmount = 1.0f; } else if (inkAmount < -1.0f) { inkAmount = -1.0f; } m_inkAmount = inkAmount; } void Bristle::setColor(const KoColor &color) { m_color = color; } void Bristle::setEnabled(bool enabled) { m_enabled = enabled; } diff --git a/plugins/paintops/hairy/bristle.h b/plugins/paintops/hairy/bristle.h index f26eca8b40..28db390ca5 100644 --- a/plugins/paintops/hairy/bristle.h +++ b/plugins/paintops/hairy/bristle.h @@ -1,119 +1,119 @@ /* * Copyright (c) 2008 Lukas Tvrdy * * 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 _BRISTLE_H_ #define _BRISTLE_H_ #include #include class Bristle { public: + Bristle() = default; Bristle(float x, float y, float length); - Bristle(); ~Bristle(); inline float x() const { return m_x; } inline float y() const { return m_y; } inline float prevX() const { return m_prevX; } inline float prevY() const { return m_prevY; } inline float length() const { return m_length; } inline const KoColor &color() const { return m_color; } inline int counter() const { return m_counter; } inline void upIncrement() { m_counter++; } inline float inkAmount() const { return m_inkAmount; }; inline float distanceCenter() { return std::sqrt(m_x * m_x + m_y * m_y); } inline void setX(float x) { m_x = x; } inline void setY(float y) { m_y = y; } inline void setPrevX(float prevX) { m_prevX = prevX; } inline void setPrevY(float prevY) { m_prevY = prevY; } inline bool enabled() const { return m_enabled; } void setLength(float length); void setColor(const KoColor &color); void addInk(float value); void removeInk(float value); void setInkAmount(float inkAmount); void setEnabled(bool enabled); private: void init(float x, float y, float length); // coordinates of bristle - float m_x; - float m_y; - float m_prevX; - float m_prevY; - float m_length; // z - coordinate + float m_x{0.0f}; + float m_y{0.0f}; + float m_prevX{0.0f}; + float m_prevY{0.0f}; + float m_length{0.0f}; // z - coordinate KoColor m_color; - float m_inkAmount; + float m_inkAmount{0.0f}; // new dimension in bristle - int m_counter; + int m_counter{0}; - bool m_enabled; + bool m_enabled{true}; }; #endif diff --git a/plugins/paintops/hairy/hairy_brush.cpp b/plugins/paintops/hairy/hairy_brush.cpp index 882065362f..b8ba438bc6 100644 --- a/plugins/paintops/hairy/hairy_brush.cpp +++ b/plugins/paintops/hairy/hairy_brush.cpp @@ -1,442 +1,441 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "hairy_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include #include HairyBrush::HairyBrush() { m_counter = 0; m_lastAngle = 0.0; m_oldPressure = 1.0f; m_saturationId = -1; m_transfo = 0; } HairyBrush::~HairyBrush() { delete m_transfo; qDeleteAll(m_bristles.begin(), m_bristles.end()); m_bristles.clear(); } void HairyBrush::initAndCache() { m_compositeOp = m_dab->colorSpace()->compositeOp(COMPOSITE_OVER); m_pixelSize = m_dab->colorSpace()->pixelSize(); if (m_properties->useSaturation) { m_transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", m_params); if (m_transfo) { m_saturationId = m_transfo->parameterId("s"); } } } void HairyBrush::fromDabWithDensity(KisFixedPaintDeviceSP dab, qreal density) { int width = dab->bounds().width(); int height = dab->bounds().height(); int centerX = width * 0.5; int centerY = height * 0.5; // make mask Bristle * bristle = 0; qreal alpha; quint8 * dabPointer = dab->data(); quint8 pixelSize = dab->pixelSize(); const KoColorSpace * cs = dab->colorSpace(); KoColor bristleColor(cs); KisRandomSource randomSource(0); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { alpha = cs->opacityF(dabPointer); if (alpha != 0.0) { if (density == 1.0 || randomSource.generateNormalized() <= density) { memcpy(bristleColor.data(), dabPointer, pixelSize); bristle = new Bristle(x - centerX, y - centerY, alpha); // using value from image as length of bristle bristle->setColor(bristleColor); m_bristles.append(bristle); } } dabPointer += pixelSize; } } } void HairyBrush::paintLine(KisPaintDeviceSP dab, KisPaintDeviceSP layer, const KisPaintInformation &pi1, const KisPaintInformation &pi2, qreal scale, qreal rotation) { m_counter++; qreal x1 = pi1.pos().x(); qreal y1 = pi1.pos().y(); qreal x2 = pi2.pos().x(); qreal y2 = pi2.pos().y(); qreal dx = x2 - x1; qreal dy = y2 - y1; // TODO:this angle is different from the drawing angle in sensor (info.angle()). The bug is caused probably due to // not computing the drag vector properly in paintBezierLine when smoothing is used //qreal angle = atan2(dy, dx); qreal angle = rotation; qreal mousePressure = 1.0; if (m_properties->useMousePressure) { // want pressure from mouse movement qreal distance = sqrt(dx * dx + dy * dy); mousePressure = (1.0 - computeMousePressure(distance)); scale *= mousePressure; } // this pressure controls shear and ink depletion qreal pressure = mousePressure * (pi2.pressure() * 2); Bristle *bristle = 0; KoColor bristleColor(dab->colorSpace()); m_dabAccessor = dab->createRandomAccessorNG((int)x1, (int)y1); m_dab = dab; // initialization block if (firstStroke()) { initAndCache(); } // if this is first time the brush touches the canvas and we use soak the ink from canvas if (firstStroke() && m_properties->useSoakInk) { if (layer) { colorifyBristles(layer, pi1.pos()); } else { dbgKrita << "Can't soak the ink from the layer"; } } KisRandomSourceSP randomSource = pi2.randomSource(); qreal fx1, fy1, fx2, fy2; qreal randomX, randomY; qreal shear; float inkDeplation = 0.0; int inkDepletionSize = m_properties->inkDepletionCurve.size(); int bristleCount = m_bristles.size(); int bristlePathSize; qreal treshold = 1.0 - pi2.pressure(); for (int i = 0; i < bristleCount; i++) { if (!m_bristles.at(i)->enabled()) continue; bristle = m_bristles[i]; randomX = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor; randomY = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor; shear = pressure * m_properties->shearFactor; m_transform.reset(); m_transform.rotateRadians(-angle); m_transform.scale(scale, scale); m_transform.translate(randomX, randomY); m_transform.shear(shear, shear); if (firstStroke() || (!m_properties->connectedPath)) { // transform start dab m_transform.map(bristle->x(), bristle->y(), &fx1, &fy1); // transform end dab m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2); } else { // continue the path of the bristle from the previous position fx1 = bristle->prevX(); fy1 = bristle->prevY(); m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2); } // remember the end point bristle->setPrevX(fx2); bristle->setPrevY(fy2); // all coords relative to device position fx1 += x1; fy1 += y1; fx2 += x2; fy2 += y2; if (m_properties->threshold && (bristle->length() < treshold)) continue; // paint between first and last dab const QVector bristlePath = m_trajectory.getLinearTrajectory(QPointF(fx1, fy1), QPointF(fx2, fy2), 1.0); bristlePathSize = m_trajectory.size(); memcpy(bristleColor.data(), bristle->color().data() , m_pixelSize); for (int i = 0; i < bristlePathSize ; i++) { if (m_properties->inkDepletionEnabled) { inkDeplation = fetchInkDepletion(bristle, inkDepletionSize); if (m_properties->useSaturation && m_transfo != 0) { saturationDepletion(bristle, bristleColor, pressure, inkDeplation); } if (m_properties->useOpacity) { opacityDepletion(bristle, bristleColor, pressure, inkDeplation); } } else { if (bristleColor.opacityU8() != 0) { bristleColor.setOpacity(bristle->length()); } } addBristleInk(bristle, bristlePath.at(i), bristleColor); bristle->setInkAmount(1.0 - inkDeplation); bristle->upIncrement(); } } m_dab = 0; m_dabAccessor = 0; } inline qreal HairyBrush::fetchInkDepletion(Bristle* bristle, int inkDepletionSize) { if (bristle->counter() >= inkDepletionSize - 1) { return m_properties->inkDepletionCurve[inkDepletionSize - 1]; } else { return m_properties->inkDepletionCurve[bristle->counter()]; } } void HairyBrush::saturationDepletion(Bristle * bristle, KoColor &bristleColor, qreal pressure, qreal inkDeplation) { qreal saturation; if (m_properties->useWeights) { // new weighted way (experiment) saturation = ( (pressure * m_properties->pressureWeight) + (bristle->length() * m_properties->bristleLengthWeight) + (bristle->inkAmount() * m_properties->bristleInkAmountWeight) + ((1.0 - inkDeplation) * m_properties->inkDepletionWeight)) - 1.0; } else { // old way of computing saturation saturation = ( pressure * bristle->length() * bristle->inkAmount() * (1.0 - inkDeplation)) - 1.0; } m_transfo->setParameter(m_transfo->parameterId("h"), 0.0); m_transfo->setParameter(m_transfo->parameterId("v"), 0.0); m_transfo->setParameter(m_saturationId, saturation); m_transfo->setParameter(3, 1);//sets the type to m_transfo->setParameter(4, false);//sets the colorize to none. m_transfo->transform(bristleColor.data(), bristleColor.data() , 1); } void HairyBrush::opacityDepletion(Bristle* bristle, KoColor& bristleColor, qreal pressure, qreal inkDeplation) { qreal opacity = OPACITY_OPAQUE_F; if (m_properties->useWeights) { - opacity = qBound(0.0, - (pressure * m_properties->pressureWeight) + - (bristle->length() * m_properties->bristleLengthWeight) + - (bristle->inkAmount() * m_properties->bristleInkAmountWeight) + - ((1.0 - inkDeplation) * m_properties->inkDepletionWeight), 1.0); - + opacity = pressure * m_properties->pressureWeight + + bristle->length() * m_properties->bristleLengthWeight + + bristle->inkAmount() * m_properties->bristleInkAmountWeight + + (1.0 - inkDeplation) * m_properties->inkDepletionWeight; } else { opacity = bristle->length() * bristle->inkAmount(); } - bristleColor.setOpacity(opacity); + + opacity = qBound(0.0, opacity, 1.0); } inline void HairyBrush::addBristleInk(Bristle *bristle,const QPointF &pos, const KoColor &color) { Q_UNUSED(bristle); if (m_properties->antialias) { if (m_properties->useCompositing) { paintParticle(pos, color); } else { paintParticle(pos, color, 1.0); } } else { int ix = qRound(pos.x()); int iy = qRound(pos.y()); if (m_properties->useCompositing) { plotPixel(ix, iy, color); } else { darkenPixel(ix, iy, color); } } } void HairyBrush::paintParticle(QPointF pos, const KoColor& color, qreal weight) { // opacity top left, right, bottom left, right quint8 opacity = color.opacityU8(); opacity *= weight; int ipx = int (pos.x()); int ipy = int (pos.y()); qreal fx = pos.x() - ipx; qreal fy = pos.y() - ipy; quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity); quint8 btr = qRound((fx) * (1.0 - fy) * opacity); quint8 bbl = qRound((1.0 - fx) * (fy) * opacity); quint8 bbr = qRound((fx) * (fy) * opacity); const KoColorSpace * cs = m_dab->colorSpace(); m_dabAccessor->moveTo(ipx , ipy); btl = quint8(qBound(OPACITY_TRANSPARENT_U8, btl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); cs->setOpacity(m_dabAccessor->rawData(), btl, 1); m_dabAccessor->moveTo(ipx + 1, ipy); btr = quint8(qBound(OPACITY_TRANSPARENT_U8, btr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); cs->setOpacity(m_dabAccessor->rawData(), btr, 1); m_dabAccessor->moveTo(ipx, ipy + 1); bbl = quint8(qBound(OPACITY_TRANSPARENT_U8, bbl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); cs->setOpacity(m_dabAccessor->rawData(), bbl, 1); m_dabAccessor->moveTo(ipx + 1, ipy + 1); bbr = quint8(qBound(OPACITY_TRANSPARENT_U8, bbr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); cs->setOpacity(m_dabAccessor->rawData(), bbr, 1); } void HairyBrush::paintParticle(QPointF pos, const KoColor& color) { // opacity top left, right, bottom left, right memcpy(m_color.data(), color.data(), m_pixelSize); quint8 opacity = color.opacityU8(); int ipx = int (pos.x()); int ipy = int (pos.y()); qreal fx = pos.x() - ipx; qreal fy = pos.y() - ipy; quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity); quint8 btr = qRound((fx) * (1.0 - fy) * opacity); quint8 bbl = qRound((1.0 - fx) * (fy) * opacity); quint8 bbr = qRound((fx) * (fy) * opacity); m_color.setOpacity(btl); plotPixel(ipx , ipy, m_color); m_color.setOpacity(btr); plotPixel(ipx + 1 , ipy, m_color); m_color.setOpacity(bbl); plotPixel(ipx , ipy + 1, m_color); m_color.setOpacity(bbr); plotPixel(ipx + 1 , ipy + 1, m_color); } inline void HairyBrush::plotPixel(int wx, int wy, const KoColor &color) { m_dabAccessor->moveTo(wx, wy); m_compositeOp->composite(m_dabAccessor->rawData(), m_pixelSize, color.data() , m_pixelSize, 0, 0, 1, 1, OPACITY_OPAQUE_U8); } inline void HairyBrush::darkenPixel(int wx, int wy, const KoColor &color) { m_dabAccessor->moveTo(wx, wy); if (m_dab->colorSpace()->opacityU8(m_dabAccessor->rawData()) < color.opacityU8()) { memcpy(m_dabAccessor->rawData(), color.data(), m_pixelSize); } } double HairyBrush::computeMousePressure(double distance) { static const double scale = 20.0; static const double minPressure = 0.02; double oldPressure = m_oldPressure; double factor = 1.0 - distance / scale; if (factor < 0.0) factor = 0.0; double result = ((4.0 * oldPressure) + minPressure + factor) / 5.0; m_oldPressure = result; return result; } void HairyBrush::colorifyBristles(KisPaintDeviceSP source, QPointF point) { KoColor bristleColor(m_dab->colorSpace()); KisCrossDeviceColorPickerInt colorPicker(source, bristleColor); Bristle *b = 0; int size = m_bristles.size(); for (int i = 0; i < size; i++) { b = m_bristles[i]; int x = qRound(b->x() + point.x()); int y = qRound(b->y() + point.y()); colorPicker.pickOldColor(x, y, bristleColor.data()); b->setColor(bristleColor); } } diff --git a/plugins/tools/basictools/CMakeLists.txt b/plugins/tools/basictools/CMakeLists.txt index 139c7193db..effd5e654b 100644 --- a/plugins/tools/basictools/CMakeLists.txt +++ b/plugins/tools/basictools/CMakeLists.txt @@ -1,47 +1,45 @@ if (NOT APPLE) add_subdirectory(tests) endif () -include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) - set(kritadefaulttools_SOURCES default_tools.cc kis_tool_colorpicker.cc kis_tool_brush.cc kis_tool_line.cc kis_tool_line_helper.cpp kis_tool_fill.cc kis_tool_rectangle.cc kis_tool_ellipse.cc kis_tool_gradient.cc kis_tool_measure.cc kis_tool_path.cc kis_tool_move.cc kis_tool_movetooloptionswidget.cpp strokes/move_stroke_strategy.cpp strokes/move_selection_stroke_strategy.cpp kis_tool_multihand.cpp kis_tool_multihand_config.cpp kis_tool_pencil.cc kis_tool_pan.cpp ) ki18n_wrap_ui(kritadefaulttools_SOURCES wdgcolorpicker.ui wdgmovetool.ui wdgmultihandtool.ui) qt5_add_resources(kritadefaulttools_SOURCES defaulttools.qrc ) add_library(kritadefaulttools MODULE ${kritadefaulttools_SOURCES}) generate_export_header(kritadefaulttools BASE_NAME kritadefaulttools) target_link_libraries(kritadefaulttools kritaui kritabasicflakes) target_link_libraries(kritadefaulttools ${Boost_SYSTEM_LIBRARY}) install(TARGETS kritadefaulttools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) ########### install files ############### install( FILES KisToolPath.action KisToolPencil.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) diff --git a/plugins/tools/tool_transform2/CMakeLists.txt b/plugins/tools/tool_transform2/CMakeLists.txt index a7248b5354..ce61896b4f 100644 --- a/plugins/tools/tool_transform2/CMakeLists.txt +++ b/plugins/tools/tool_transform2/CMakeLists.txt @@ -1,51 +1,49 @@ if (NOT WIN32 AND NOT APPLE) add_subdirectory(tests) endif() -include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) - set(kritatooltransform_SOURCES tool_transform.cc tool_transform_args.cc kis_transform_mask_adapter.cpp kis_animated_transform_parameters.cpp tool_transform_changes_tracker.cpp kis_tool_transform.cc kis_tool_transform_config_widget.cpp kis_transform_strategy_base.cpp kis_warp_transform_strategy.cpp kis_cage_transform_strategy.cpp kis_simplified_action_policy_strategy.cpp kis_liquify_transform_strategy.cpp kis_liquify_paint_helper.cpp kis_liquify_paintop.cpp kis_liquify_properties.cpp kis_free_transform_strategy.cpp kis_free_transform_strategy_gsl_helpers.cpp kis_perspective_transform_strategy.cpp kis_transform_utils.cpp kis_modify_transform_mask_command.cpp strokes/transform_stroke_strategy.cpp kis_transform_args_keyframe_channel.cpp ) qt5_add_resources(kritatooltransform_SOURCES tool_transform.qrc) ki18n_wrap_ui(kritatooltransform_SOURCES wdg_tool_transform.ui) add_library(kritatooltransform MODULE ${kritatooltransform_SOURCES}) generate_export_header(kritatooltransform BASE_NAME kritatooltransform) if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Transform Tool will not be able to scale the image with handles. Please install GSL library.") target_link_libraries(kritatooltransform kritaui) else () target_link_libraries(kritatooltransform kritaui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () install(TARGETS kritatooltransform DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( FILES KisToolTransform.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) diff --git a/plugins/tools/tool_transform2/tests/CMakeLists.txt b/plugins/tools/tool_transform2/tests/CMakeLists.txt index 4b9d0a88b4..9eda85766e 100644 --- a/plugins/tools/tool_transform2/tests/CMakeLists.txt +++ b/plugins/tools/tool_transform2/tests/CMakeLists.txt @@ -1,19 +1,18 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/.. ${CMAKE_SOURCE_DIR}/sdk/tests - ${Boost_INCLUDE_DIRS} ) macro_add_unittest_definitions() ########### next target ############### add_test(test_save_load_transform_args.cpp TEST_NAME krita-ui-TestSaveLoadTransformArgs LINK_LIBRARIES kritatooltransform kritaui kritaimage Qt5::Test) add_test(test_animated_transform_parameters.cpp TEST_NAME krita-ui-TestAnimatedTransformParameters LINK_LIBRARIES kritatooltransform kritaui kritaimage Qt5::Test)