diff --git a/3rdparty/README.md b/3rdparty/README.md index be2ce3f3cc..e44f733b82 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,255 +1,241 @@ = CMake external projects to build krita's dependencies on Linux, Windows or OSX = If you need to build Krita's dependencies for the following reasons: -* you develop on Windows and aren't using Emerge +* you develop on Windows and aren't using Craft * you develop on OSX and aren't using Homebrew * you want to build a generic, distro-agnostic version of Krita for Linux * you develop on Linux, but some dependencies aren't available for your distribution and you know what you're doing, you can use the following guide to build the dependencies that Krita needs. -If you develop on Linux and your distribution has the dependencies available, +If you develop on Linux and your distribution has all dependencies available, YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW Otherwise you risk major confusion. == Prerequisites == Note: on all operating systems the entire procedure is done in a terminal window. 1. git: https://git-scm.com/downloads. Make sure git is in your path -2. cmake 3.3.2: https://cmake.org/download/. Make sure cmake is in your path. +2. cmake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path. 3. Make sure you have a compiler: * Linux: gcc, minimum version 4.8 * OSX: clang, you need to install xcode for this * Windows: mingw-w64 5.4 (by mingw-builds) - 32-bit (x86) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/5.4.0/threads-posix/dwarf/ - 64-bit (x64) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/5.4.0/threads-posix/seh/ Make sure mingw's bin folder is in your path. It might be a good idea to create a batch file which sets the path and start cmd. MSVC is *not* supported at the moment. 4. On Windows, you will also need Python 3.6: https://www.python.org. Make sure to have python.exe in your path. This version of python will be used for two things: to configure Qt and to build the python scripting module. Make sure the version you download is exactly python-3.6.1. Make sure Python is in your path. == Setup your environment == == Prepare your directory layout == 1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace BUILDROOT with $BUILDROOT or %BUILDROOT whenever you copy and paste a command, depending on your operating system. 2. Checkout krita in BUILDROOT cd BUILDROOT git clone git://anongit.kde.org/krita.git 3. Create the build directory mkdir BUILDROOT/b 4. Create the downloads directory mkdir BUILDROOT/d 5. Create the install directory mkdir BUILDROOT/i == Prepare the externals build == 1. enter the BUILDROOT/b directory -2. The cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d. - - set PATH=BUILDROOT\i\bin\;BUILDROOT\i\lib;%PATH% - cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles" - -3. run cmake: +2. run cmake: * Linux: export PATH=$BUILDROOT/i/bin export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty \ -DINSTALL_ROOT=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DCMAKE_INSTALL_PREFIX=BUILDROOT/i * OSX: export PATH=$BUILDROOT/i/bin export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DINSTALL_ROOT=$BUILDROOT/i * Windows 32 bits: TODO * Windows 64 bits: Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d. set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH% set PYTHONHOME=%BUILDROOT%/i (only if you want to build your own python) set PATH=BUILDROOT\i\bin\;BUILDROOT\i\lib;%PATH% cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles" -4. build the packages: +3. build the packages: With a judicious application of DEPENDS statements, it's possible to build it all in one go, but in my experience that fails always, so it's better to build the dependencies independently. If you want to use the included version of Python (can be used on Windows to build Qt instead of installing Python separately): cmake --build . --config RelWithDebInfo --target ext_python On Windows: cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico cmake --build . --config RelWithDebInfo --target ext_gettext On all operating systems: cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_zlib cmake --build . --config RelWithDebInfo --target ext_boost Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost cmake --build . --config RelWithDebInfo --target ext_eigen3 cmake --build . --config RelWithDebInfo --target ext_exiv2 cmake --build . --config RelWithDebInfo --target ext_fftw3 On Windows: set FFTW_LIB_DIR=%BUILDROOT%\i\lib dlltool.exe -k --output-lib %FFTW_LIB_DIR%\libfftw3-3.a --input-def %FFTW_LIB_DIR%\libfftw3-3.def dlltool.exe -k --output-lib %FFTW_LIB_DIR%\libfftw3f-3.a --input-def %FFTW_LIB_DIR%\libfftw3f-3.def dlltool.exe -k --output-lib %FFTW_LIB_DIR%\libfftw3l-3.a --input-def %FFTW_LIB_DIR%\libfftw3l-3.def On all operating systems cmake --build . --config RelWithDebInfo --target ext_ilmbase cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr Note for OSX: On OSX, you need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again. install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups On All operating systems: cmake --build . --config RelWithDebInfo --target ext_png cmake --build . --config RelWithDebInfo --target ext_tiff cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw On Windows cmake --build . --config RelWithDebInfo --target ext_freetype cmake --build . --config RelWithDebInfo --target ext_poppler On Linux cmake --build . --config RelWithDebInfo --target ext_kcrash Everywhere else: cmake --build . --config RelWithDebInfo --target ext_kwindowsystem On Windows, if you want to include DrMingw for dumping backtrace on crash: cmake --build . --config RelWithDebInfo --target ext_drmingw Note: poppler should be buildable on Linux as well with a home-built freetype and fontconfig, but I don't know how to make fontconfig find freetype, and on Linux, fontconfig is needed for poppler. Poppler is needed for PDF import. Note 2: libcurl still isn't available. Note 3: if you want to build a release, you need to get the binary gettext archives from files.kde.org/krita/build/dependencies: http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-32.zip http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-64.zip Take care, these zips contain a libstdc++-6.dll that you don't want in your path when building. == Build Krita == 1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build 3. Run On Windows Depending on what you want to use, run this command for MSBuild: cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 Or this to use jom (faster compiling, uses all cores, ships with QtCreator/pre-built Qt binaries): cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 On Linux cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfobg On OSX cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo 4. Run On Linux and OSX make make install On Windows Either use MSBuild to build (-- /m tells msbuild to use all your cores): cmake --build . --config RelWithDebInfo --target INSTALL -- /m Or use jom which should be in a path similar to C:\Qt\Qt5.6.0\Tools\QtCreator\bin\jom.exe. So, from the same folder, instead of running cmake run: "C:\Qt\Qt5.6.0\Tools\QtCreator\bin\jom.exe" install 6. Run krita: On Linux BUILDROOT/i/bin/krita On Windows BUILDROOT\i\bin\krita.exe On OSX BUILDROOT/i/bin/krita.app/Contents/MacOS/krita == Packaging a Windows Build == -If you want to create a stripped down version of Krita to distribute, after building everything just copy the makepkg.bat file from the "windows" folder inside krita root source folder to BUILDROOT and run it. - -That will copy the necessary files into the specified folder and leave behind developer related files, so the resulting folder will be a smaller install folder. - -== Common Issues == - -- On Windows, if you get a 'mspdb140.dll' missing alert window, it means you did not run the bat file. Make sure to include the quotes in the command: - "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat" - -- On Windows, if you get an error about Qt5Core.dll missing/not found or nmake exit with an error that mention QT_PLUGIN_PATH, you have to copy a couple of dlls in the Qt build directory, look for the N.B. in the Qt instructions at the start of the Readme. +If you want to create a stripped down version of Krita to distribute, after building everything just copy the package_2.cmd file from the "windows" folder inside krita root source folder to BUILDROOT and run it (most likely C:\dev\). -- If you receive an error while compiling about "missing QtCore5.cmake", or something similar, check to make sure qmake is in your PATH. Restart your command line after any changes are made. +That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version. diff --git a/3rdparty/ext_png/CMakeLists.txt b/3rdparty/ext_png/CMakeLists.txt index b5ac62d0f6..3af51c3677 100755 --- a/3rdparty/ext_png/CMakeLists.txt +++ b/3rdparty/ext_png/CMakeLists.txt @@ -1,12 +1,12 @@ SET(PREFIX_ext_png "${EXTPREFIX}" ) ExternalProject_Add( ext_png DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/libpng-1.6.23.tar.gz URL_MD5 3a8fb380b0fb39cb69efe47901917e38 INSTALL_DIR ${PREFIX_ext_png} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_png} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_png} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DPNG_TESTS=OFF UPDATE_COMMAND "" DEPENDS ext_zlib ) diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index 3d05473fef..f4fadb83e6 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,183 +1,183 @@ SET(EXTPREFIX_qt "${EXTPREFIX}") if (WIN32) 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.zip URL_MD5 9d7ea0cadcec7b5a63e8e83686756978 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 INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure.bat -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-angle -no-ssl -no-openssl -no-wmf-backend -no-qml-debug -no-libproxy -no-system-proxies -no-nis -no-icu -no-mtdev -opensource -confirm-license -release -opengl desktop -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -prefix ${EXTPREFIX_qt} -platform win32-g++ # use this line for building Qt with debugging info enabled #CONFIGURE_COMMAND /configure.bat -release -force-debug-info -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-angle -no-ssl -no-openssl -no-wmf-backend -no-qml-debug -no-libproxy -no-system-proxies -no-nis -no-icu -no-mtdev -opensource -confirm-license -release -opengl desktop -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -prefix ${EXTPREFIX_qt} -platform win32-g++ BUILD_COMMAND mingw32-make INSTALL_COMMAND mingw32-make install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 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 + 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) 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) 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" ) + MATH( EXPR NUM_CORES "${NUM_CORES} - 2" ) endif() set(PARALLEL_MAKE "make;-j${NUM_CORES}") message(STATUS "${EXTPREFIX_qt}:Parallelized make: ${PARALLEL_MAKE}") else() set(PARALLEL_MAKE "make") endif() ExternalProject_Add(ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} LOG_DOWNLOAD ON LOG_UPDATE ON LOG_CONFIGURE ON LOG_BUILD ON LOG_TEST ON LOG_INSTALL ON BUILD_IN_SOURCE ON URL https://download.qt.io/official_releases/qt/5.7/5.7.0/single/qt-everywhere-opensource-src-5.7.0.tar.gz URL_MD5 9a46cce61fc64c20c3ac0a0e0fa41b42 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} BUILD_COMMAND ${PARALLEL_MAKE} INSTALL_COMMAND make install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 8025323834..a57345e8b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,660 +1,666 @@ 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 ## ########################################################### find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## -find_package(ECM 1.7.0 REQUIRED NOMODULE) +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 ) 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}) find_package (KUserFeedback) 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 -DQT_DISABLE_DEPRECATED_BEFORE=0 ) 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() # 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 REQUIRED COMPONENTS system) # for pigment and stage include_directories(${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 REQUIRED "3.0") set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## set(EXIV2_MIN_VERSION "0.16") find_package(Exiv2 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 REQUIRED "2.4") 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") elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast -fPIC") 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) +find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for pthreads (for G'Mic) ## find_package(Threads) set_package_properties(Threads PROPERTIES DESCRIPTION "PThreads - A low-level threading library" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic plugin") ## ## Test for OpenMP (for G'Mic) ## find_package(OpenMP) set_package_properties(OpenMP PROPERTIES DESCRIPTION "A low-level parallel execution library" URL "http://openmp.org/wp/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic plugin") ## ## Test for Curl (for G'Mic) ## find_package(CURL) set_package_properties(CURL PROPERTIES DESCRIPTION "A tool to fetch remote data" URL "http://curl.haxx.se/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic plugin") ############################ ############################# ## 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/README.md b/README.md index ea84a029de..9aa3143c9c 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,47 @@ ![Picture](https://krita.org/wp-content/uploads/2016/04/krita_logo_200-ef21fd67a8add4f0.png) Krita is a free and open source digital painting application. It is for artists who want to create professional work from start to end. Krita is used by comic book artists, illustrators, concept artists, matte and texture painters and in the digital VFX industry. If you are reading this on Github, be aware that this is just a mirror. Our real code repository is provided by KDE: https://phabricator.kde.org/source/krita/ This repository contains the current, Qt5-based, development version of Krita 3. Krita 3.0 has been released and development on Krita 2.9 has stopped. Krita 2.9 was part of Calligra: https://phabricator.kde.org/source/krita/ -![Picture] (https://krita.org/wp-content/uploads/2016/04/krita-30-screenshot.jpg) +![Picture](https://krita.org/wp-content/uploads/2016/04/krita-30-screenshot.jpg) ### User Manual https://docs.krita.org/Category:User_Manual ### Development Notes and Build Instructions If you're building on Windows or OSX you'll need to build some third-party dependencies first. You should look at the README in the 3rdparty folder for directions. If you're building on Linux, please follow David Revoy's Cat Guide: http://www.davidrevoy.com/article193/guide-building-krita-on-linux-for-cats Other developer guides, notes and wiki: https://community.kde.org/Krita Apidox: https://api.kde.org/bundled-apps-api/krita-apidocs/ ### Bugs and Wishes https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1315444&product=krita&query_format=advanced ### Discussion Forum http://forum.kde.org/viewforum.php?f=136 ### IRC channel Most of the developers hang out here. If you are interested in helping with the project this is a great place to start. Many of the developers based in Europe so they may be offline depending on when you join. irc.freenode.net, #krita ### Project Website http://www.krita.org ### License Krita as a whole is licensed under the GNU Public License, Version 3. Individual files may have a different, but compatible license. diff --git a/benchmarks/kis_projection_benchmark.cpp b/benchmarks/kis_projection_benchmark.cpp index 3a1ff0b043..1bb98f2d0d 100644 --- a/benchmarks/kis_projection_benchmark.cpp +++ b/benchmarks/kis_projection_benchmark.cpp @@ -1,62 +1,67 @@ /* * Copyright (c) 2010 Lukáš Tvrdý lukast.dev@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kis_projection_benchmark.h" #include "kis_benchmark_values.h" #include #include #include #include #include #include void KisProjectionBenchmark::initTestCase() { } void KisProjectionBenchmark::cleanupTestCase() { } void KisProjectionBenchmark::benchmarkProjection() { - QBENCHMARK{ - KisDocument *doc = KisPart::instance()->createDocument(); - doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"); - doc->image()->refreshGraph(); - doc->exportDocument(QUrl::fromLocalFile(QString(FILES_OUTPUT_DIR) + QDir::separator() + "save_test.kra")); - delete doc; + KisDocument *doc = KisPart::instance()->createDocument(); + doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"); + + qApp->processEvents(); + QTest::qWait(100); + + QBENCHMARK_ONCE { + doc->image()->refreshGraphAsync(); + doc->image()->waitForDone(); } + + delete doc; } void KisProjectionBenchmark::benchmarkLoading() { QBENCHMARK{ KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"); delete doc2; } } QTEST_MAIN(KisProjectionBenchmark) diff --git a/cmake/modules/FindPoppler.cmake b/cmake/modules/FindPoppler.cmake deleted file mode 100644 index ba2fff22ad..0000000000 --- a/cmake/modules/FindPoppler.cmake +++ /dev/null @@ -1,106 +0,0 @@ -# - Try to find the poppler PDF library -# Once done this will define -# -# POPPLER_FOUND - system has poppler -# POPPLER_INCLUDE_DIR - the poppler include directory -# POPPLER_LIBRARY - Link this to use poppler -# - -# Copyright (c) 2006-2010, Pino Toscano, -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -if(POPPLER_INCLUDE_DIR AND POPPLER_LIBRARY) - - # in cache already - set(POPPLER_FOUND TRUE) - -else() - -set(_poppler_version_bad FALSE) - -if(NOT WIN32) - # use pkg-config to get the directories and then use these values - # in the FIND_PATH() and FIND_LIBRARY() calls - include(FindPkgConfig) - pkg_check_modules(_pc_poppler poppler-qt5) - if(_pc_poppler_FOUND) - if(NOT "${_pc_poppler_VERSION}" VERSION_GREATER 0.5.3) - set(_poppler_version_bad TRUE) - endif() - endif() -endif() - -if(NOT _poppler_version_bad) - set(POPPLER_FOUND FALSE) - - find_library(POPPLER_LIBRARY poppler-qt5 - HINTS ${_pc_poppler_LIBRARY_DIRS} - ) - find_library(POPPLER_CORE_LIBRARY poppler - HINTS ${_pc_poppler_LIBRARY_DIRS} - ) - - find_path(POPPLER_INCLUDE_DIR poppler-qt5.h - HINTS ${_pc_poppler_INCLUDE_DIRS} - PATH_SUFFIXES poppler/qt5 - ) - find_path(POPPLER_INCLUDE_DIR_core qt5/poppler-qt5.h - HINTS ${_pc_poppler_INCLUDE_DIRS} - PATH_SUFFIXES poppler - ) - - if(POPPLER_LIBRARY AND POPPLER_INCLUDE_DIR AND POPPLER_INCLUDE_DIR_core) - list(APPEND POPPLER_INCLUDE_DIR "${POPPLER_INCLUDE_DIR_core}") - set(POPPLER_FOUND TRUE) - endif() -endif() - -if (POPPLER_FOUND) - include(CheckCXXSourceCompiles) - - set(CMAKE_REQUIRED_INCLUDES ${POPPLER_INCLUDE_DIR} ${QT_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${POPPLER_LIBRARY} ${POPPLER_CORE_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY}) - -check_cxx_source_compiles(" -#include -int main() -{ - Poppler::Document::RenderHint hint = Poppler::Document::ThinLineSolid; - return 0; -} -" HAVE_POPPLER_0_24) - -check_cxx_source_compiles(" -#include -int main() -{ - Poppler::Page *p = 0; - p->annotations( QSet() << Poppler::Annotation::ASound ); - return 0; -} -" HAVE_POPPLER_0_28) - - set(CMAKE_REQUIRED_INCLUDES) - set(CMAKE_REQUIRED_LIBRARIES) - if (HAVE_POPPLER_0_28) - set(popplerVersionMessage "0.28") - elseif (HAVE_POPPLER_0_24) - set(popplerVersionMessage "0.24") - endif () - if (NOT Poppler_FIND_QUIETLY) - message(STATUS "Found Poppler-Qt5: ${POPPLER_LIBRARY}, (>= ${popplerVersionMessage})") - endif () -else () - if (Poppler_FIND_REQUIRED) - message(FATAL_ERROR "Could NOT find Poppler-Qt5") - endif () - message(STATUS "Could not find OPTIONAL package Poppler-Qt5") -endif () - -# ensure that they are cached -set(POPPLER_INCLUDE_DIR ${POPPLER_INCLUDE_DIR} CACHE INTERNAL "The Poppler-Qt5 include path") -set(POPPLER_LIBRARY ${POPPLER_LIBRARY} CACHE INTERNAL "The Poppler-Qt5 library") - -endif() diff --git a/krita/data/CMakeLists.txt b/krita/data/CMakeLists.txt index 74adbd2497..ade756f352 100644 --- a/krita/data/CMakeLists.txt +++ b/krita/data/CMakeLists.txt @@ -1,22 +1,23 @@ add_subdirectory( actions ) add_subdirectory( brushes ) add_subdirectory( bundles ) add_subdirectory( patterns ) add_subdirectory( gradients ) add_subdirectory( profiles ) add_subdirectory( templates ) add_subdirectory( workspaces ) add_subdirectory( themes ) add_subdirectory( predefined_image_sizes ) add_subdirectory( input ) add_subdirectory( shortcuts ) add_subdirectory( paintoppresets ) add_subdirectory( palettes ) +add_subdirectory( symbols ) ########### install files ############### install( FILES kritarc DESTINATION ${CONFIG_INSTALL_DIR} ) diff --git a/krita/data/symbols/BalloonSymbols.svg b/krita/data/symbols/BalloonSymbols.svg new file mode 100644 index 0000000000..b2563e47ab --- /dev/null +++ b/krita/data/symbols/BalloonSymbols.svg @@ -0,0 +1,145 @@ + + + Word Balloons + Ballons for holding text. + + + + image/svg+xml + + Word Balloons + 2013-04-22 + + + Martin Owens, Tavmjong Bah + + + + + Public Domain + + + English + + + word + balloon + comic + cartoon + speach + exclaim + + + + + + + + + + + + + + + Thought Balloon + + + + + + + + + + + + + Dream Speaking + + + + + + + + + + Rounded Balloon + + + + + + + + + + Squared Balloon + + + + + + + + + + Over the Phone + + + + + + + + + + Hip Balloon + + + + + + + + + + Circle Balloon + + + + + + + + + + Exclaim Balloon + + + + + + + + + + + + + + + + + diff --git a/krita/data/symbols/CMakeLists.txt b/krita/data/symbols/CMakeLists.txt new file mode 100644 index 0000000000..483d7399e5 --- /dev/null +++ b/krita/data/symbols/CMakeLists.txt @@ -0,0 +1,6 @@ +install( FILES + BalloonSymbols.svg + pepper_carrot_speech_bubbles.svg + DESTINATION ${DATA_INSTALL_DIR}/krita/symbols) + + diff --git a/krita/data/symbols/pepper_carrot_speech_bubbles.svg b/krita/data/symbols/pepper_carrot_speech_bubbles.svg new file mode 100644 index 0000000000..a15e6be7b0 --- /dev/null +++ b/krita/data/symbols/pepper_carrot_speech_bubbles.svg @@ -0,0 +1,4625 @@ + + + + + Pepper & Carrot Speech Bubbles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default non-flowed. + + + Shhh Shhh Shhh Shhh + + + Crrr Crrr CrrrCrrr Crrr Shhh Shhh ShhhShhh Shhh + + Zoooouuuu ! + + Crrr Crrr Crrr + Shhh Shhh ShhhShhh Shhh Crrr Crrr CrrrCrrr Crrr + + Shhh Crrr Crrr + + + + + + + + + + + + + + + + + + + + Gargblr + + S P l + + SPLASH + + SHH SHH + + ZOMBIFICATION ! + + + + + + + + B lup B lup + + BOUCHER + + + Safran + + + GO!! + + + PAF! + + + piou + + + S S S P l o p + + BZZZIIO + + CRiCRiCRiiiiiiiiiiiiiiiiiiiiii + + Glou + + + Glou Glou Glou + + BrrOoooOoo!! + + + CrrAsh!! + + + * default description + + + + + + + + + Shrrrrrrr + + + + + + + + + + image/svg+xml + + Pepper & Carrot Speech Bubbles + + 2017-06-08 + + + David Revoy + + + + + Wolthera van Hövell tot Westerflier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/krita/data/templates/animation/.directory b/krita/data/templates/animation/.directory index 351b8d2e3f..a462f96be0 100644 --- a/krita/data/templates/animation/.directory +++ b/krita/data/templates/animation/.directory @@ -1,17 +1,18 @@ [Desktop Entry] Name=Animation Templates Name[ca]=Plantilles d'animació Name[ca@valencia]=Plantilles d'animació Name[cs]=Šablony animací: Name[de]=Animations-Vorlagen Name[en_GB]=Animation Templates Name[es]=Plantillas de animación Name[it]=Modelli di animazioni Name[nl]=Animatiesjablonen Name[pl]=Szablony animacji Name[pt]=Modelos de Animações Name[pt_BR]=Modelos de animação Name[sv]=Animeringsmallar +Name[tr]=Canlandırma Şablonları Name[uk]=Шаблони анімацій Name[x-test]=xxAnimation Templatesxx X-KDE-DefaultTab=true diff --git a/krita/data/templates/animation/Anim-Jp-EN.desktop b/krita/data/templates/animation/Anim-Jp-EN.desktop index 46f2baa38f..84fb9a835f 100644 --- a/krita/data/templates/animation/Anim-Jp-EN.desktop +++ b/krita/data/templates/animation/Anim-Jp-EN.desktop @@ -1,23 +1,24 @@ [Desktop Entry] Type=Link URL=.source/Anim-Jp-EN.kra Icon=template_animation Name=Animation-Japanese-En Name[ca]=Animació-Japonès-EN Name[ca@valencia]=Animació-Japonés-EN Name[de]=Animation-Japanisch-En Name[en_GB]=Animation-Japanese-En Name[es]=Animación-Japonés-En Name[et]=Animation-Japanese-En Name[it]=Animazione-Giapponese-EN Name[ja]=日本式アニメ(英語版) Name[nl]=Animatie-Japans-En Name[pl]=Animacja-Japońska-En Name[pt]=Animação-Japonês-EN Name[pt_BR]=Animation-Japanese-En Name[ru]=Анимация-японская-англ Name[sk]=Animation-Japanese-En Name[sv]=Animering-japanska-en +Name[tr]=Canlandırma-Japonca-İngilizce Name[uk]=Японська анімація (англійською) Name[x-test]=xxAnimation-Japanese-Enxx Name[zh_CN]=日本动画 (英式) diff --git a/krita/data/templates/animation/Anim-Jp-JP.desktop b/krita/data/templates/animation/Anim-Jp-JP.desktop index 166deb207f..ecfc3e629d 100644 --- a/krita/data/templates/animation/Anim-Jp-JP.desktop +++ b/krita/data/templates/animation/Anim-Jp-JP.desktop @@ -1,23 +1,24 @@ [Desktop Entry] Type=Link URL=.source/Anim-Jp-JP.kra Icon=template_animation Name=Animation-Japanese-JP Name[ca]=Animació-Japonès-JP Name[ca@valencia]=Animació-Japonés-JP Name[de]=Animation-Japanisch-JP Name[en_GB]=Animation-Japanese-JP Name[es]=Animación-Japonés-JP Name[et]=Animation-Japanese-JP Name[it]=Animazione-Giapponese-JP Name[ja]=日本式アニメ(日本語版) Name[nl]=Animatie-Japans-JP Name[pl]=Animacja-Japońska-JP Name[pt]=Animação-Japonês-JP Name[pt_BR]=Animation-Japanese-JP Name[ru]=Анимация-японская-японск Name[sk]=Animation-Japanese-JP Name[sv]=Animering-japanska-jp +Name[tr]=Canlandırma-Japonca-JP Name[uk]=Японська анімація (японською) Name[x-test]=xxAnimation-Japanese-JPxx Name[zh_CN]=日本动画 (日式) diff --git a/krita/data/templates/comics/BD-EuroTemplate.desktop b/krita/data/templates/comics/BD-EuroTemplate.desktop index 04f0f9c6ae..ce68d34a2c 100644 --- a/krita/data/templates/comics/BD-EuroTemplate.desktop +++ b/krita/data/templates/comics/BD-EuroTemplate.desktop @@ -1,73 +1,74 @@ [Desktop Entry] Type=Link URL=.source/BD-EuroTemplate.kra Icon=template_comics_empty Name=European BD template Name[bs]=Evropski BD predložak Name[ca]=Plantilla europea BD Name[ca@valencia]=Plantilla europea BD Name[da]=Europæisk BD-skabelon Name[de]=Europäische „Bande Dessinée (BD)“-Vorlage Name[el]=Ευρωπαϊκό BD πρότυπο Name[en_GB]=European BD template Name[es]=plantilla de cómic europeo Name[et]=Euroopa BD mall Name[eu]=Europako BD-txantiloia Name[fi]=Eurooppalainen BD-pohja Name[fr]=Modèle européen de bandes dessinées Name[gl]=Formato europeo (2×4 viñetas) Name[hu]=Európai BD sablon Name[it]=Modello MD europeo Name[ja]=バンドデシネテンプレート Name[kk]=Еуропалық BD үлгісі Name[lt]=Europos DB šablonas Name[nb]=Europeisk BD-mal Name[nds]=Europääsch BD-Vörlaag Name[nl]=Europees BD-sjabloon Name[pl]=Europejski szablon BD Name[pt]=Modelo de BD Europeia Name[pt_BR]=Modelo Europeu BD Name[ru]=Шаблон в европейском стиле (BD) Name[sk]=Európska BD šablóna Name[sl]=Evropska predloga BD Name[sv]=Europeisk BD-mall Name[tr]=Avrupa BD Şablonu Name[uk]=Європейський шаблон BD Name[wa]=Modele di binde d' imådje a l' uropeyinne Name[x-test]=xxEuropean BD templatexx Name[zh_CN]=欧洲 BD 模板 Name[zh_TW]=歐式 BD 樣本 Comment=template for European BD-style comics Comment[bs]=predložak za evropske BD stripove Comment[ca]=plantilla per a còmics d'estil BD europeu Comment[ca@valencia]=plantilla per a còmics d'estil BD europeu Comment[da]=Skabelon til tegneserier i europæisk BD-stil Comment[de]=Vorlage für Comics im europäischen „Bande Dessinée“-Stil Comment[el]=πρότυπο για Ευρωπαϊκά BD-style κόμικς Comment[en_GB]=template for European BD-style comics Comment[es]=plantilla para cómics de estilo europeo Comment[et]=Euroopa BD-stiilis koomiksi mall Comment[eu]=Europako BD estiloko komikietarako txantiloia Comment[fi]=Eurooppalaisen BD-tyylin sarjakuvan pohja Comment[fr]=Modèle européen de bandes dessinées Comment[gl]=Páxina de banda deseñada de formato europeo, con 2×4 viñetas regulares. Comment[hu]=sablon az európai BD-stílusú képregényekhez Comment[it]=modello per fumetti in stile BD europeo Comment[ja]=バンドデシネ式コミック用テンプレート Comment[kk]=Еуропалық BD-стильдегі комикс үлгісі Comment[nb]=mal for europeiske tegneserier i BD-stil Comment[nds]=BD-Vörlaag för europääsche Comics Comment[nl]=sjabloon voor Europese strips in BD-stijl Comment[pl]=szablon dla Europejskiego stylu komików BD Comment[pt]=modelo de banda desenhada do estilo Europeu Comment[pt_BR]=Modelo de quadrinhos no estilo Europeu BD Comment[ru]=Шаблон комиксов в европейском стиле (BD) Comment[sk]=šablóna pre európske BD komixy Comment[sl]=predloga za stripe v evropskem slogu BD Comment[sv]=seriemall med europeisk BD-stil +Comment[tr]=Avrupa BD tarzı çizgi romanlar için şablon Comment[uk]=шаблон для європейських коміксів у стилі BD Comment[wa]=Modele po les bindes d' imådje al môde uropeyinne Comment[x-test]=xxtemplate for European BD-style comicsxx Comment[zh_CN]=欧洲 BD 式漫画模板 Comment[zh_TW]=歐式 BD-樣式漫畫的樣本 X-Krita-Version=28 diff --git a/krita/data/templates/comics/Comics-USTemplate.desktop b/krita/data/templates/comics/Comics-USTemplate.desktop index 561e281151..f3de5e702d 100644 --- a/krita/data/templates/comics/Comics-USTemplate.desktop +++ b/krita/data/templates/comics/Comics-USTemplate.desktop @@ -1,76 +1,78 @@ [Desktop Entry] Type=Link URL=.source/Comics-USTemplate.kra Icon=template_comics_empty Name=US-style comics template Name[bs]=Američki strip predložak Name[ca]=plantilla de còmics d'estil americà Name[ca@valencia]=plantilla de còmics d'estil americà Name[cs]=Šablona komixu v americkém stylu Name[da]=Tegneserieskabelon i amerikansk stil Name[de]=US-Design-Comicvorlage Name[el]=Πρότυπο κόμικς US-style Name[en_GB]=US-style comics template Name[es]=plantilla de cómic de estilo estadounidense Name[et]=USA stiilis koomiksi mall Name[eu]=AEBko estiloko komiki-txantiloia Name[fi]=Yhdysvaltalaistyylinen sarjakuvapohja Name[fr]=Modèle US de bande dessinée Name[gl]=Formato estadounidense (2×3 viñetas) Name[hu]=US-stílusú képregénysablon Name[it]=Modello per fumetti in stile americano Name[ja]=アメリカ式コミックテンプレート Name[kk]=АҚШ-стильді комикс үлгісі Name[ko]=미국식 만화 서식 Name[lt]=JAV stiliaus komiksų šablonas Name[nb]=Tegneseriemal i USA-stil Name[nds]=Amerikaansch Comicvörlaag Name[nl]=sjabloon voor strips in US-stijl Name[pl]=Szablon komiksów Amerykańskiego stylu Name[pt]=Modelo de banda desenhada dos EUA Name[pt_BR]=Modelo de quadrinhos no estilo americano Name[ru]=Шаблон в американском стиле Name[sk]=šablóna pre americké komixy Name[sl]=Predloga za stripe v ameriškem slogu Name[sv]=Seriemall med amerikansk stil +Name[tr]=US tarzı çizgi roman şablonu Name[uk]=Шаблон коміксів у американському стилі Name[wa]=Modele comics a l' amerikinnes Name[x-test]=xxUS-style comics templatexx Name[zh_CN]=美式漫画模板 Name[zh_TW]=美式漫畫樣本 Comment=template for US-style comics Comment[bs]=predložak za stripove američkog stila Comment[ca]=plantilla per a còmics d'estil americà Comment[ca@valencia]=plantilla per a còmics d'estil americà Comment[cs]=šablona pro komiksy v americkém stylu Comment[da]=skabelon til tegneserier i amerikansk stil Comment[de]=Vorlage für Comics im US-Stil Comment[el]=πρότυπο για US-style κόμικς Comment[en_GB]=template for US-style comics Comment[es]=plantilla para cómics de estilo estadounidense Comment[et]=USA stiilis koomiksi mall Comment[eu]=AEBko estiloko komikietarako txantiloia Comment[fi]=yhdysvaltalaistyylisen sarjakuvan pohja Comment[fr]=Modèle US de bandes dessinées Comment[gl]=Páxina de banda deseñada de formato estadounidense, con 2×3 viñetas regulares. Comment[hu]=sablon a US-stílusú képregényekhez Comment[it]=modello per fumetti in stile americano Comment[ja]=アメリカ式コミック用テンプレート Comment[kk]=АҚШ-стильдегі комикс үлгісі Comment[ko]=미국식 만화 서식 Comment[nb]=mal for tegneserier i US-stil Comment[nds]=Vörlaag för amerikaansche Comics Comment[nl]=sjabloon voor strips in US-stijl Comment[pl]=szablon dla Amerykańskiego stylu komiksów Comment[pt]=modelo de banda desenhada do estilo dos EUA Comment[pt_BR]=Modelo de quadrinhos no estilo americano Comment[ru]=Шаблон комиксов в американском стиле Comment[sk]=šablóna pre americké komixy Comment[sl]=predloga za stripe v ameriškem slogu Comment[sv]=seriemall med amerikansk stil +Comment[tr]=US tarzı çizgi romanlar için şablon Comment[uk]=шаблон для коміксів у американському стилі Comment[wa]=Modele di bindes d' imådje al môde des comics amerikins Comment[x-test]=xxtemplate for US-style comicsxx Comment[zh_CN]=美式漫画模板 Comment[zh_TW]=美式漫畫樣本 X-Krita-Version=28 diff --git a/krita/data/templates/comics/a4_waffle_grid.desktop b/krita/data/templates/comics/a4_waffle_grid.desktop index 1ebaea23e9..8cc814294a 100644 --- a/krita/data/templates/comics/a4_waffle_grid.desktop +++ b/krita/data/templates/comics/a4_waffle_grid.desktop @@ -1,65 +1,67 @@ [Desktop Entry] Type=Link URL=.source/a4_waffle_grid.kra Icon=template_comics_empty Name=waffle-iron grid Name[bs]=mreža sječenog željeza Name[ca]=graella de ferro Name[ca@valencia]=graella de ferro Name[da]=vaffeljernsgitter Name[de]=Waffeleisengitter Name[el]=waffle-iron κάνναβος Name[en_GB]=waffle-iron grid Name[es]=rejilla de hierro para gofres Name[et]=Vahvlimasina ruudustik Name[eu]=gofreetarako burdinazko sareta Name[fr]=Grille en métal-gaufré Name[gl]=Grade de 3×5 viñetas Name[it]=Griglia a wafer Name[ja]=格子状コマ Name[kk]=торлы көзді Name[nb]=vaffeljern-rutenett Name[nds]=Wafeliesengadder Name[nl]=wafelijzer-raster Name[pl]=siatka gofrownicy Name[pt]=grelha de ferro para 'waffles' Name[pt_BR]=Grade de ferro vazia Name[ru]=Страница с ячейками Name[sk]=vaflovo-železná mriežka Name[sv]=våffelmönster +Name[tr]=waffle-çelik ızgara Name[uk]=сітка з комірками Name[wa]=grile di fier a wåfes Name[x-test]=xxwaffle-iron gridxx Name[zh_CN]=华夫铁网格 Name[zh_TW]=鐵模式格線 Comment=300 dpi, A4 waffle-iron grid comic page with ink and color layers Comment[bs]=300 dpi, A4 mreža sječenog željeza stranica stripa s slojevima za tintu i bojemreža sječenog željeza Comment[ca]=300 ppp, pàgina de còmic amb graella de ferro amb capes de tinta i color Comment[ca@valencia]=300 ppp, pàgina de còmic amb graella de ferro amb capes de tinta i color Comment[da]=300 dpi, A4 tegneserieside i vaffeljernsgitter med blæk og farvelag Comment[de]=Comicseite mit Waffeleisengitter-Muster, Tinten- und Farbebenen. Format A4, Auflösung 300 dpi. Comment[el]=300 dpi, σελίδα κόμικ A4 με waffle-iron κάνναβο και στρώματα μελάνης και χρώματος Comment[en_GB]=300 dpi, A4 waffle-iron grid comic page with ink and colour layers Comment[es]=página de cómic con rejilla de hierro para gofres de tamaño A4, a 300 ppp, con tinta y capas de colores Comment[et]=300 DPI A4 vahvlimasina ruudustikuga koomiksilehekülg tindi- ja värvikihiga Comment[eu]=Gofreetarako burdinazko sareta duen 300 dpi-ko A4 komiki-orria, tinta- eta kolore-geruzaduna Comment[fr]=Page de bande dessinée avec Grille en métal-gaufré de 300 dpi, A4 avec encre et calques colorés Comment[gl]=Páxina de banda deseñada en A4 a 300 dpi con 3×5 viñetas regulares e capas de tinta e cor. Comment[it]=Pagina di fumetti con griglia a wafer a 300 dpi, A4, con livelli per inchiostro e colore Comment[ja]=300 dpi A4 サイズの、ペン入れレイヤーと彩色レイヤーを備えた格子状コマテンプレート Comment[kk]=300 н/д A4 торлы көзді парақтағы комикс Comment[nb]=300 dpi, A4 tegneserieside med vaffeljern-rutenett, med tusj- og fargelag Comment[nds]=300 dpi, A4 Wafeliesengadder-Comicsiet mit Dint un Klöörlagen. Comment[nl]=300 dpi, A4 wafelijzer-raster strippagina met inkt en kleurlagen Comment[pl]=300 dpi, strona A4 siatki gofrownicy z warstwami tuszu i koloru Comment[pt]=banda desenhada A4, em grelha de 'waffle' a 300 ppp, com camadas de cores e de pinturas Comment[pt_BR]=Página de quadrinhos A4, em grade de ferro a 300 ppp, com camadas de cores e de pinturas Comment[ru]=300 dpi, страница комикса в формате A4 с ячейками и слоями контуров и цветов Comment[sk]=300 dpi, A4 vaflovo železná mriežka komiksovej strany s atramentom a farebnými vrstvami Comment[sv]=300 punkter/tum, A4 våffelmönstrad seriesida med bläck- och färglager +Comment[tr]=300 dpi, A4 waffle-çelik ızgara mürekkep ve renk katmanlı çizgi roman sayfası Comment[uk]=300 т/д, сторінка коміксу у форматі A4 з комірками та шарами контурів та кольорів Comment[wa]=Pådje A4 di binde d' imådjes avou on discôpaedje come ene grile di fier a wåfes avou des coûtches d' intche eyet d' coleurs. Comment[x-test]=xx300 dpi, A4 waffle-iron grid comic page with ink and color layersxx Comment[zh_CN]=300 DPI,A4 尺寸华夫铁网格漫画页,带有墨水和色彩图层 Comment[zh_TW]=300 dpi,A4 大小的烘餅鐵模狀的格線,有墨水與顏色圖層 X-Krita-Version=28 diff --git a/krita/krita.action b/krita/krita.action index 5e041115ce..90b5abdf15 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,2946 +1,3006 @@ 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 Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Painting Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false 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 Increase opacity Increase opacity Increase opacity 0 0 O false 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 &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I 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 &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 Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false 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 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 Ruler assistant editor tool Ruler assistant editor 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 + &Lock/unlock layer + + Lock/unlock layer + Lock/unlock layer + 1000 + 0 + + false + + + + visible + Toggle layer &visibility + + Toggle layer visibility + Toggle layer visibility + 1000 + 0 + + false + + + + transparency-locked + Lock/unlock layer &alpha + + Lock/unlock layer's alpha + Lock/unlock layer's 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 10000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 10000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 10000 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 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/krita.xmlgui b/krita/krita.xmlgui index 4d8e4f1aee..2ecb866d19 100644 --- a/krita/krita.xmlgui +++ b/krita/krita.xmlgui @@ -1,377 +1,378 @@ &File &Edit &View &Canvas - - + + &Snap To - - - - - - - - + + + + + + + + &Image &Rotate &Layer New &Import/Export Import &Convert &Select &Group &Transform - - - - - &Rotate - - - - - - + + + + + &Rotate + + + + + + - - + + S&plit - - S&plit Alpha - - - - - + + S&plit Alpha + + + + + &Select Filte&r &Tools Recording Macros Setti&ngs &Help + File Brushes and Stuff diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index e634ae6ded..3eb2dc9814 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,165 +1,166 @@ org.kde.krita.desktop CC0-1.0 Krita Krita Krita Krita Krita + Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Digital Painting, Creative Freedom Pintura dixital, llibertá creativa Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pittura digitale, libertà creativa Digital Painting, Creative Freedom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura Digital, Liberdade Criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 数码绘图,自由创作

Krita is the full-featured digital art studio.

Krita ye l'estudiu completu d'arte dixital.

Krita je potpuni digitalni umjetnički studio.

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

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

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita on täyspiirteinen digitaiteen ateljee.

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

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita è uno studio d'arte digitale completo.

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

Krita is de digitale kunststudio vol mogelijkheden.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

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

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

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

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

Krita är den fullfjädrade digitala konststudion.

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

xxKrita is the full-featured digital art studio.xx

Krita 是全功能的数码艺术工作室。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

适合做草图和绘画,为艺术大师提供了从草稿到数码绘画的完整解决方案。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Krita 是创建抽象艺术,漫画,渲染纹理和亚光绘画的理想选择。Krita 支持非常多的色彩空间,比如 8 位和 16 位整数通道以及 16 位和 32 位浮点通道的 RGB 和 CMYK。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

尽情使用高级笔刷引擎,超赞的滤镜和很多手绘特性,发挥 Krita 绝佳的创造力。

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_002.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_003.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_004.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_005.png foundation@krita.org KDE krita
diff --git a/krita/pics/misc-dark/misc-dark-icons.qrc b/krita/pics/misc-dark/misc-dark-icons.qrc index 2bb29be62b..62db775c2a 100644 --- a/krita/pics/misc-dark/misc-dark-icons.qrc +++ b/krita/pics/misc-dark/misc-dark-icons.qrc @@ -1,55 +1,54 @@ dark_draw-eraser.svg dark_geometry.svg dark_ox16-action-object-align-horizontal-center-calligra.svg dark_ox16-action-object-align-horizontal-left-calligra.svg dark_ox16-action-object-align-horizontal-right-calligra.svg dark_ox16-action-object-align-vertical-bottom-calligra.svg dark_ox16-action-object-align-vertical-center-calligra.svg dark_ox16-action-object-align-vertical-top-calligra.svg dark_ox16-action-object-order-back-calligra.svg dark_ox16-action-object-order-front-calligra.svg dark_ox16-action-object-order-lower-calligra.svg dark_ox16-action-object-order-raise-calligra.svg dark_ox16-action-object-group-calligra.svg dark_ox16-action-object-ungroup-calligra.svg dark_distribute-horizontal-center.svg dark_distribute-horizontal-left.svg dark_distribute-horizontal-right.svg dark_distribute-horizontal.svg dark_distribute-vertical-bottom.svg dark_distribute-vertical-center.svg dark_distribute-vertical-top.svg dark_distribute-vertical.svg dark_paintop_settings_01.svg dark_paintop_settings_02.svg dark_pivot-point.svg dark_stroke-cap-butt.svg dark_stroke-cap-round.svg dark_stroke-cap-square.svg dark_stroke-join-bevel.svg dark_stroke-join-miter.svg dark_stroke-join-round.svg dark_symmetry-horizontal.svg dark_symmetry-vertical.svg dark_onionOff.svg dark_onionOn.svg dark_onion_skin_options.svg - dark_onion_skin_options.svg dark_path-break-point.svg dark_path-break-segment.svg dark_pathpoint-corner.svg dark_pathpoint-curve.svg dark_pathpoint-insert.svg dark_pathpoint-join.svg dark_pathpoint-line.svg dark_pathpoint-merge.svg dark_pathpoint-remove.svg dark_pathpoint-smooth.svg dark_pathpoint-symmetric.svg dark_pathsegment-curve.svg dark_pathsegment-line.svg diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index d810eae7c0..3042da88c9 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,231 +1,234 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceManager.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp KoShapeBasedDocumentBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoUnavailShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp KoOdfGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoSvgPaste.cpp KoDragOdfSaveHelper.cpp KoShapeOdfSaveHelper.cpp KoConnectionPoint.cpp KoConnectionShape.cpp KoConnectionShapeLoadingUpdater.cpp KoConnectionShapeFactory.cpp KoConnectionShapeConfigWidget.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp KoOdfWorkaround.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoConnectionShapeTypeCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoShapeConnectionChangeCommand.cpp commands/KoMultiPathPointMergeCommand.cpp commands/KoMultiPathPointJoinCommand.cpp tools/KoCreateShapeStrategy.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathConnectionPointStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoPanTool.cpp tools/KoPanToolFactory.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp tools/KoCreateShapesTool.cpp tools/KoCreateShapesToolFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp + resources/KoSvgSymbolCollectionResource.cpp + FlakeDebug.cpp tests/MockShapes.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui KoConnectionShapeConfigWidget.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) + diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp index 7cc545349e..5ab19095b7 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp @@ -1,373 +1,408 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2009 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2007-2010 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 "KoCanvasControllerWidgetViewport_p.h" #include #include #include #include #include #include #include #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeFactoryBase.h" // for the SHAPE mimetypes #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoCanvasBase.h" #include "KoShapeLayer.h" #include "KoShapePaintingContext.h" #include "KoToolProxy.h" #include "KoCanvasControllerWidget.h" #include "KoViewConverter.h" - +#include "KoSvgPaste.h" // ********** Viewport ********** Viewport::Viewport(KoCanvasControllerWidget *parent) - : QWidget(parent) - , m_draggedShape(0) - , m_drawShadow(false) - , m_canvas(0) - , m_documentOffset(QPoint(0, 0)) - , m_margin(0) + : QWidget(parent) + , m_draggedShape(0) + , m_drawShadow(false) + , m_canvas(0) + , m_documentOffset(QPoint(0, 0)) + , m_margin(0) { setAutoFillBackground(true); setAcceptDrops(true); setMouseTracking(true); m_parent = parent; } void Viewport::setCanvas(QWidget *canvas) { if (m_canvas) { m_canvas->hide(); delete m_canvas; } m_canvas = canvas; if (!canvas) return; m_canvas->setParent(this); m_canvas->show(); if (!m_canvas->minimumSize().isNull()) { m_documentSize = m_canvas->minimumSize(); } resetLayout(); } void Viewport::setDocumentSize(const QSize &size) { m_documentSize = size; resetLayout(); } void Viewport::documentOffsetMoved(const QPoint &pt) { m_documentOffset = pt; resetLayout(); } void Viewport::setDrawShadow(bool drawShadow) { m_drawShadow = drawShadow; } void Viewport::handleDragEnterEvent(QDragEnterEvent *event) { // if not a canvas set then ignore this, makes it possible to assume // we have a canvas in all the support methods. if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) { event->ignore(); return; } + delete m_draggedShape; + m_draggedShape = 0; + // only allow dropping when active layer is editable KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); KoShapeLayer *activeLayer = selection->activeLayer(); if (activeLayer && (!activeLayer->isEditable() || activeLayer->isGeometryProtected())) { event->ignore(); return; } const QMimeData *data = event->mimeData(); + if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) || - data->hasFormat(SHAPEID_MIMETYPE)) { - QByteArray itemData; - bool isTemplate = true; - if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) - itemData = data->data(SHAPETEMPLATE_MIMETYPE); - else { - isTemplate = false; - itemData = data->data(SHAPEID_MIMETYPE); + data->hasFormat(SHAPEID_MIMETYPE) || + data->hasFormat("image/svg+xml")) + { + if (data->hasFormat("image/svg+xml")) { + KoCanvasBase *canvas = m_parent->canvas(); + QSizeF fragmentSize; + + QList shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"), + canvas->shapeController()->documentRectInPixels(), + canvas->shapeController()->pixelsPerInch(), + &fragmentSize); + + if (!shapes.isEmpty()) { + m_draggedShape = shapes[0]; + } } - QDataStream dataStream(&itemData, QIODevice::ReadOnly); - QString id; - dataStream >> id; - QString properties; - if (isTemplate) - dataStream >> properties; - - // and finally, there is a point. - QPointF offset; - dataStream >> offset; - - // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy - // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave) - KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); - if (! factory) { - warnFlake << "Application requested a shape that is not registered '" << - id << "', Ignoring"; - event->ignore(); - return; + else { + QByteArray itemData; + bool isTemplate = true; + + if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) { + itemData = data->data(SHAPETEMPLATE_MIMETYPE); + } + else if (data->hasFormat(SHAPEID_MIMETYPE)) { + isTemplate = false; + itemData = data->data(SHAPEID_MIMETYPE); + } + + + QDataStream dataStream(&itemData, QIODevice::ReadOnly); + QString id; + dataStream >> id; + QString properties; + if (isTemplate) + dataStream >> properties; + + // and finally, there is a point. + QPointF offset; + dataStream >> offset; + + // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy + // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave) + KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); + if (! factory) { + warnFlake << "Application requested a shape that is not registered '" << + id << "', Ignoring"; + event->ignore(); + return; + } + if (isTemplate) { + KoProperties props; + props.load(properties); + m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager()); + } + else { + m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager()); + } + + if (m_draggedShape->shapeId().isEmpty()) { + m_draggedShape->setShapeId(factory->id()); + } } + event->setDropAction(Qt::CopyAction); event->accept(); - if (isTemplate) { - KoProperties props; - props.load(properties); - m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager()); - } else - m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager()); - Q_ASSERT(m_draggedShape); if (!m_draggedShape) return; - if (m_draggedShape->shapeId().isEmpty()) - m_draggedShape->setShapeId(factory->id()); m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_parent->canvas()->shapeManager()->addShape(m_draggedShape); } else { event->ignore(); } } void Viewport::handleDropEvent(QDropEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos())); return; } repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc. m_draggedShape->setPosition(QPointF(0, 0)); // always save position. QPointF newPos = correctPosition(event->pos()); m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document. m_draggedShape->setAbsolutePosition(newPos); + + KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape); + if (cmd) { m_parent->canvas()->addCommand(cmd); KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); // repaint selection before selecting newly create shape - Q_FOREACH (KoShape * shape, selection->selectedShapes()) + Q_FOREACH (KoShape * shape, selection->selectedShapes()) { shape->update(); + } selection->deselectAll(); selection->select(m_draggedShape); - } else + } else { + delete m_draggedShape; + } m_draggedShape = 0; } QPointF Viewport::correctPosition(const QPoint &point) const { QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y()); correctedPos += m_documentOffset; return m_parent->canvas()->viewToDocument(correctedPos); } void Viewport::handleDragMoveEvent(QDragMoveEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos())); return; } m_draggedShape->update(); repaint(m_draggedShape); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_draggedShape->update(); repaint(m_draggedShape); } void Viewport::repaint(KoShape *shape) { QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x()); rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y()); rect.adjust(-2, -2, 2, 2); // adjust for antialias update(rect); } void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event) { if (m_draggedShape) { repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); delete m_draggedShape; m_draggedShape = 0; } else { m_parent->canvas()->toolProxy()->dragLeaveEvent(event); } } void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event) { Q_UNUSED(event); // Draw the shadow around the canvas. if (m_parent->canvas() && m_parent->canvas()->canvasWidget() && m_drawShadow) { QWidget *canvas = m_parent->canvas()->canvasWidget(); painter.setPen(QPen(Qt::black, 0)); QRect rect(canvas->x(), canvas->y(), canvas->width(), canvas->height()); rect.adjust(-1, -1, 0, 0); painter.drawRect(rect); painter.drawLine(rect.right() + 2, rect.top() + 2, rect.right() + 2, rect.bottom() + 2); painter.drawLine(rect.left() + 2, rect.bottom() + 2, rect.right() + 2, rect.bottom() + 2); } if (m_draggedShape) { const KoViewConverter *vc = m_parent->canvas()->viewConverter(); painter.save(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. painter.translate(canvasWidget->x() - m_documentOffset.x(), - canvasWidget->y() - m_documentOffset.y()); + canvasWidget->y() - m_documentOffset.y()); QPointF offset = vc->documentToView(m_draggedShape->position()); painter.setOpacity(0.6); painter.translate(offset.x(), offset.y()); painter.setRenderHint(QPainter::Antialiasing); KoShapePaintingContext paintContext; //FIXME m_draggedShape->paint(painter, *vc, paintContext); painter.restore(); } } void Viewport::resetLayout() { // Determine the area we have to show QRect viewRect(m_documentOffset, size()); const int viewH = viewRect.height(); const int viewW = viewRect.width(); const int docH = m_documentSize.height(); const int docW = m_documentSize.width(); int moveX = 0; int moveY = 0; int resizeW = viewW; int resizeH = viewH; -// debugFlake <<"viewH:" << viewH << endl -// << "docH: " << docH << endl -// << "viewW: " << viewW << endl -// << "docW: " << docW << endl; + // debugFlake <<"viewH:" << viewH << endl + // << "docH: " << docH << endl + // << "viewW: " << viewW << endl + // << "docW: " << docW << endl; if (viewH == docH && viewW == docW) { // Do nothing resizeW = docW; resizeH = docH; } else if (viewH > docH && viewW > docW) { // Show entire canvas centered moveX = (viewW - docW) / 2; moveY = (viewH - docH) / 2; resizeW = docW; resizeH = docH; } else if (viewW > docW) { // Center canvas horizontally moveX = (viewW - docW) / 2; resizeW = docW; int marginTop = m_margin - m_documentOffset.y(); int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); if (marginTop > 0) moveY = marginTop; if (marginTop > 0) resizeH = viewH - marginTop; if (marginBottom > 0) resizeH = viewH - marginBottom; } else if (viewH > docH) { // Center canvas vertically moveY = (viewH - docH) / 2; resizeH = docH; int marginLeft = m_margin - m_documentOffset.x(); int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); if (marginLeft > 0) moveX = marginLeft; if (marginLeft > 0) resizeW = viewW - marginLeft; if (marginRight > 0) resizeW = viewW - marginRight; } else { // Take care of the margin around the canvas int marginTop = m_margin - m_documentOffset.y(); int marginLeft = m_margin - m_documentOffset.x(); int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); if (marginTop > 0) moveY = marginTop; if (marginLeft > 0) moveX = marginLeft; if (marginTop > 0) resizeH = viewH - marginTop; if (marginLeft > 0) resizeW = viewW - marginLeft; if (marginRight > 0) resizeW = viewW - marginRight; if (marginBottom > 0) resizeH = viewH - marginBottom; } if (m_parent->canvasMode() == KoCanvasController::AlignTop) { // have up to m_margin pixels at top. moveY = qMin(m_margin, moveY); } if (m_canvas) { QRect geom; if (m_parent->canvasMode() == KoCanvasController::Infinite) geom = QRect(0, 0, viewW, viewH); else geom = QRect(moveX, moveY, resizeW, resizeH); if (m_canvas->geometry() != geom) { m_canvas->setGeometry(geom); m_canvas->update(); } } if (m_drawShadow) { update(); } emit sizeChanged(); #if 0 - debugFlake <<"View port geom:" << geometry(); - if (m_canvas) + debugFlake <<"View port geom:" << geometry(); + if (m_canvas) debugFlake <<"Canvas widget geom:" << m_canvas->geometry(); #endif } diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp index 08d5ccd328..c3be65f33f 100644 --- a/libs/flake/KoDrag.cpp +++ b/libs/flake/KoDrag.cpp @@ -1,112 +1,113 @@ /* This file is part of the KDE project * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2009 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 "KoDrag.h" #include "KoDragOdfSaveHelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoShapeSavingContext.h" #include #include #include class KoDragPrivate { public: KoDragPrivate() : mimeData(0) { } ~KoDragPrivate() { delete mimeData; } QMimeData *mimeData; }; KoDrag::KoDrag() : d(new KoDragPrivate()) { } KoDrag::~KoDrag() { delete d; } bool KoDrag::setSvg(const QList originalShapes) { QRectF boundingRect; QList shapes; Q_FOREACH (KoShape *shape, originalShapes) { boundingRect |= shape->boundingRect(); shapes.append(shape->cloneShape()); } qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QBuffer buffer; QLatin1String mimeType("image/svg+xml"); buffer.open(QIODevice::WriteOnly); const QSizeF pageSize(boundingRect.right(), boundingRect.bottom()); SvgWriter writer(shapes, pageSize); writer.save(buffer); buffer.close(); + qDeleteAll(shapes); setData(mimeType, buffer.data()); return true; } void KoDrag::setData(const QString &mimeType, const QByteArray &data) { if (d->mimeData == 0) { d->mimeData = new QMimeData(); } d->mimeData->setData(mimeType, data); } void KoDrag::addToClipboard() { if (d->mimeData) { QApplication::clipboard()->setMimeData(d->mimeData); d->mimeData = 0; } } QMimeData * KoDrag::mimeData() { QMimeData *mimeData = d->mimeData; d->mimeData = 0; return mimeData; } diff --git a/libs/flake/KoFilterEffect.h b/libs/flake/KoFilterEffect.h index f0173f79c5..2817f2861c 100644 --- a/libs/flake/KoFilterEffect.h +++ b/libs/flake/KoFilterEffect.h @@ -1,170 +1,170 @@ /* This file is part of the KDE project * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_FILTER_EFFECT_H_ #define _KO_FILTER_EFFECT_H_ class QImage; class QString; class QRectF; class KoXmlWriter; class KoFilterEffectRenderContext; class KoFilterEffectLoadingContext; -class KoXmlElement; +#include #include "kritaflake_export.h" #include /** * This is the base for filter effect (blur, invert...) that can be applied on a shape. * All sizes and coordinates of the filter effect are stored in object bounding box * coordinates, where (0,0) refers to the top-left corner of a shapes bounding rect * and (1,1) refers to the bottom-right corner. * When loading, a transformation matrix is given to convert from user space coordinates. * Another transformation matrix is given via the render context to convert back to * user space coordinates when applying the effect. * Using object bounding box coordinates internally makes it easy to share effects * between shapes or even between users via the filter effect resources. */ class KRITAFLAKE_EXPORT KoFilterEffect { public: KoFilterEffect(const QString &id, const QString &name); virtual ~KoFilterEffect(); /// Returns the user visible name of the filter QString name() const; /// Returns the unique id of the filter QString id() const; /// Sets the region the filter is applied to in bounding box units void setFilterRect(const QRectF &filterRect); /// Returns the region this filter is applied to in bounding box units QRectF filterRect() const; /// Returns the region this filter is applied to for the given bounding rect QRectF filterRectForBoundingRect(const QRectF &boundingRect) const; /** * Sets the name of the output image * * The name is used so that other effects can reference * the output of this effect as one of their input images. * * @param output the output image name */ void setOutput(const QString &output); /// Returns the name of the output image QString output() const; /** * Returns list of named input images of this filter effect. * * These names identify the input images for this filter effect. * These can be one of the keywords SourceGraphic, SourceAlpha, * BackgroundImage, BackgroundAlpha, FillPaint or StrokePaint, * as well as a named output of another filter effect in the stack. * An empty input list of the first effect in the stack default to * SourceGraphic, whereas on subsequent effects it defaults to the * result of the previous filter effect. */ QList inputs() const; /// Adds a new input at the end of the input list void addInput(const QString &input); /// Inserts an input at the giben position in the input list void insertInput(int index, const QString &input); /// Sets an existing input to a new value void setInput(int index, const QString &input); /// Removes an input from the given position in the input list void removeInput(int index); /** * Return the required number of input images. * The default required number of input images is 1. * Derived classes should call setRequiredInputCount to set * a different number. */ int requiredInputCount() const; /** * Returns the maximal number of input images. * The default maximal number of input images is 1. * Derived classes should call setMaximalInputCount to set * a different number. */ int maximalInputCount() const; /** * Apply the effect on an image. * @param image the image the filter should be applied to * @param context the render context providing additional data */ virtual QImage processImage(const QImage &image, const KoFilterEffectRenderContext &context) const = 0; /** * Apply the effect on a list of images. * @param images the images the filter should be applied to * @param context the render context providing additional data */ virtual QImage processImages(const QList &images, const KoFilterEffectRenderContext &context) const; /** * Loads data from given xml element. * @param element the xml element to load data from * @param context the loading context providing additional data * @return true if loading was successful, else false */ virtual bool load(const KoXmlElement &element, const KoFilterEffectLoadingContext &context) = 0; /** * Writes custom data to given xml element. * @param writer the xml writer to write data to */ virtual void save(KoXmlWriter &writer) = 0; protected: /// Sets the required number of input images void setRequiredInputCount(int count); /// Sets the maximal number of input images void setMaximalInputCount(int count); /** * Saves common filter attributes * * Saves result, subregion and input attributes. The input attrinbute * is only saved if required, maximal and actual input count equals 1. * All other filters have to write inputs on their own. */ void saveCommonAttributes(KoXmlWriter &writer); private: class Private; Private* const d; }; #endif // _KO_FILTER_EFFECT_H_ diff --git a/libs/flake/KoFilterEffectRegistry.h b/libs/flake/KoFilterEffectRegistry.h index cd1cbb3643..594d3d2116 100644 --- a/libs/flake/KoFilterEffectRegistry.h +++ b/libs/flake/KoFilterEffectRegistry.h @@ -1,61 +1,61 @@ /* 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 Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOFILTEREFFECTREGISTRY_H #define KOFILTEREFFECTREGISTRY_H #include #include #include "kritaflake_export.h" -class KoXmlElement; +#include class KoFilterEffectLoadingContext; class KoFilterEffect; class KRITAFLAKE_EXPORT KoFilterEffectRegistry : public KoGenericRegistry { public: KoFilterEffectRegistry(); ~KoFilterEffectRegistry() override; /** * Return the only instance of KoFilterEffectRegistry. * Creates an instance on the first call. */ static KoFilterEffectRegistry *instance(); /** * Creates filter effect from given xml element. * @param element the xml element to load form * @return the created filter effect if successful, otherwise returns 0 */ KoFilterEffect *createFilterEffectFromXml(const KoXmlElement &element, const KoFilterEffectLoadingContext &context); private: KoFilterEffectRegistry(const KoFilterEffectRegistry&); KoFilterEffectRegistry operator=(const KoFilterEffectRegistry&); void init(); class Private; Private * const d; }; #endif // KOFILTEREFFECTREGISTRY_H diff --git a/libs/flake/KoFrameShape.h b/libs/flake/KoFrameShape.h index 062bfd2ffd..466a85e1e4 100644 --- a/libs/flake/KoFrameShape.h +++ b/libs/flake/KoFrameShape.h @@ -1,94 +1,94 @@ /* This file is part of the KDE project Copyright (C) 2008 Thorsten Zachmann 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 KOFRAMESHAPE_H #define KOFRAMESHAPE_H #include "kritaflake_export.h" class KoShapeLoadingContext; -class KoXmlElement; +#include class QString; /** * @brief Base class for shapes that are saved as a part of a draw:frame. * * Shapes like the TextShape or the PictureShape are implementing this * class to deal with frames and their attributes. * * What follows is a sample taken out of an ODT-file that shows how this works * together; * @code * * * * @endcode * * The loading code of the shape gets passed the draw:frame element. Out of this element the * odf attributes can be loaded. Then it calls loadOdfFrame which loads the correct frame element * the object supports. The loading of the frame element is done in the loadOdfFrameElement. * * @code * bool PictureShape::loadOdf( const KoXmlElement & element, KoShapeLoadingContext &context ) * { * loadOdfAttributes( element, context, OdfAllAttributes ); * return loadOdfFrame( element, context ); * } * @endcode */ class KRITAFLAKE_EXPORT KoFrameShape { public: /** * Constructor. * * \param ns The namespace. E.g. KoXmlNS::draw * \param element The tag-name. E.g. "image" */ KoFrameShape(const QString &ns, const QString &tag); /** * Destructor. */ virtual ~KoFrameShape(); /** * Loads the content of the draw:frame element and it's children. This * method calls the abstract loadOdfFrameElement() method. * * @return false if loading failed */ virtual bool loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context); protected: /** * Abstract method to handle loading of the defined inner element like * e.g. the draw:image element. * @return false if loading failed */ virtual bool loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; private: class Private; Private * const d; }; #endif /* KOFRAMESHAPE_H */ diff --git a/libs/flake/KoMarker.h b/libs/flake/KoMarker.h index a50bc27b4f..2413ced08b 100644 --- a/libs/flake/KoMarker.h +++ b/libs/flake/KoMarker.h @@ -1,122 +1,123 @@ /* This file is part of the KDE project Copyright (C) 2011 Thorsten Zachmann 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 KOMARKER_H #define KOMARKER_H #include #include #include "kritaflake_export.h" #include -class KoXmlElement; +#include + class KoShapeLoadingContext; class KoShapeSavingContext; class QString; class QPainterPath; class KoShape; class QPainter; class KoShapeStroke; class KRITAFLAKE_EXPORT KoMarker : public QSharedData { public: KoMarker(); ~KoMarker(); /** * Display name of the marker * * @return Display name of the marker */ QString name() const; KoMarker(const KoMarker &rhs); bool operator==(const KoMarker &other) const; enum MarkerCoordinateSystem { StrokeWidth, UserSpaceOnUse }; void setCoordinateSystem(MarkerCoordinateSystem value); MarkerCoordinateSystem coordinateSystem() const; static MarkerCoordinateSystem coordinateSystemFromString(const QString &value); static QString coordinateSystemToString(MarkerCoordinateSystem value); void setReferencePoint(const QPointF &value); QPointF referencePoint() const; void setReferenceSize(const QSizeF &size); QSizeF referenceSize() const; bool hasAutoOtientation() const; void setAutoOrientation(bool value); // measured in radians! qreal explicitOrientation() const; // measured in radians! void setExplicitOrientation(qreal value); void setShapes(const QList &shapes); QList shapes() const; /** * @brief paintAtOrigin paints the marker at the position \p pos. * Scales and rotates the masrker if needed. */ void paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle); /** * Return maximum distance that the marker can take outside the shape itself */ qreal maxInset(qreal strokeWidth) const; /** * Bounding rect of the marker in local coordinates. It is assumed that the marker * is painted with the reference point placed at position (0,0) */ QRectF boundingRect(qreal strokeWidth, qreal nodeAngle) const; /** * Outline of the marker in local coordinates. It is assumed that the marker * is painted with the reference point placed at position (0,0) */ QPainterPath outline(qreal strokeWidth, qreal nodeAngle) const; /** * Draws a preview of the marker in \p previewRect of \p painter */ void drawPreview(QPainter *painter, const QRectF &previewRect, const QPen &pen, KoFlake::MarkerPosition position); void applyShapeStroke(KoShape *shape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle); private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoMarker*) #endif /* KOMARKER_H */ diff --git a/libs/flake/KoMarkerCollection.cpp b/libs/flake/KoMarkerCollection.cpp index 22c53dffca..f723f34dee 100644 --- a/libs/flake/KoMarkerCollection.cpp +++ b/libs/flake/KoMarkerCollection.cpp @@ -1,142 +1,139 @@ /* This file is part of the KDE project Copyright (C) 2011 Thorsten Zachmann 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 "KoMarkerCollection.h" #include #include #include "KoMarker.h" #include #include #include #include #include #include #include #include "kis_debug.h" // WARNING: there is a bug in GCC! It doesn't warn that we are // deleting an uninitialized type here! #include class Q_DECL_HIDDEN KoMarkerCollection::Private { public: ~Private() { } QList > markers; }; KoMarkerCollection::KoMarkerCollection(QObject *parent) : QObject(parent) , d(new Private) { // Add no marker so the user can remove a marker from the line. d->markers.append(QExplicitlySharedDataPointer(0)); // Add default markers loadDefaultMarkers(); } KoMarkerCollection::~KoMarkerCollection() { delete d; } void KoMarkerCollection::loadMarkersFromFile(const QString &svgFile) { QFile file(svgFile); if (!file.exists()) return; if (!file.open(QIODevice::ReadOnly)) return; - QXmlStreamReader reader(&file); - reader.setNamespaceProcessing(false); - QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(&file, false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << svgFile << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); return; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values parser.setXmlBaseDir(QFileInfo(svgFile).absolutePath()); parser.setFileFetcher( [](const QString &fileName) { QFile file(fileName); if (!file.exists()) return QByteArray(); file.open(QIODevice::ReadOnly); return file.readAll(); }); QSizeF fragmentSize; QList shapes = parser.parseSvg(doc.documentElement(), &fragmentSize); qDeleteAll(shapes); Q_FOREACH (const QExplicitlySharedDataPointer &marker, parser.knownMarkers()) { addMarker(marker.data()); } } void KoMarkerCollection::loadDefaultMarkers() { QString filePath = KoResourcePaths::findResource("data", "styles/markers.svg"); loadMarkersFromFile(filePath); } QList KoMarkerCollection::markers() const { QList markerList; foreach (const QExplicitlySharedDataPointer& m, d->markers){ markerList.append(m.data()); } return markerList; } KoMarker * KoMarkerCollection::addMarker(KoMarker *marker) { foreach (const QExplicitlySharedDataPointer& m, d->markers) { if (marker == m.data()) { return marker; } if (m && *marker == *m) { debugFlake << "marker is the same as other"; return m.data(); } } d->markers.append(QExplicitlySharedDataPointer(marker)); return marker; } diff --git a/libs/flake/KoMarkerCollection.h b/libs/flake/KoMarkerCollection.h index bd74001047..1f7115419d 100644 --- a/libs/flake/KoMarkerCollection.h +++ b/libs/flake/KoMarkerCollection.h @@ -1,68 +1,68 @@ /* This file is part of the KDE project Copyright (C) 2011 Thorsten Zachmann 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 KOMARKERCOLLECTION_H #define KOMARKERCOLLECTION_H #include "kritaflake_export.h" #include #include #include #include class KoMarker; -class KoXmlElement; +#include class KoShapeLoadingContext; class KRITAFLAKE_EXPORT KoMarkerCollection : public QObject { Q_OBJECT public: explicit KoMarkerCollection(QObject *parent = 0); ~KoMarkerCollection() override; QList markers() const; /** * Add marker to collection * * The collection checks if a marker with the same content exists and if so deletes the * passed marker and returns a pointer to an existing marker. If no such marker exists it * adds the marker and return the same pointer as passed. * Calling that function passes ownership of the marker to this class. * * @param marker Marker to add * @return pointer to marker that should be used. This might be different to the marker passed */ KoMarker * addMarker(KoMarker *marker); void loadMarkersFromFile(const QString &svgFile); private: /// load the markers that are available per default. void loadDefaultMarkers(); class Private; Private * const d; }; Q_DECLARE_METATYPE(KoMarkerCollection *) #endif /* KOMARKERCOLLECTION_H */ diff --git a/libs/flake/KoOdfGradientBackground.h b/libs/flake/KoOdfGradientBackground.h index 5b097f69db..d08f237fa5 100644 --- a/libs/flake/KoOdfGradientBackground.h +++ b/libs/flake/KoOdfGradientBackground.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project * * Copyright (C) 2011 Lukáš Tvrdý * * 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 KOODFGRADIENTBACKGROUND_H #define KOODFGRADIENTBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" class QImage; class KoOdfGradientBackgroundPrivate; -class KoXmlElement; +#include class KoGenStyles; class KoGenStyle; /// Gradients from odf that are not native to Qt class KoOdfGradientBackground : public KoShapeBackground { public: // constructor KoOdfGradientBackground(); // destructor ~KoOdfGradientBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// reimplemented from KoShapeBackground void fillStyle(KoGenStyle& style, KoShapeSavingContext& context) override; /// reimplemented from KoShapeBackground bool loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) override; /// reimplemented from KoShapeBackground void paint(QPainter& painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath& fillPath) const override; private: bool loadOdf(const KoXmlElement &element); void saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const; void renderSquareGradient(QImage &buffer) const; void renderRectangleGradient(QImage &buffer) const; private: void debug() const; private: Q_DECLARE_PRIVATE(KoOdfGradientBackground) Q_DISABLE_COPY(KoOdfGradientBackground) }; #endif diff --git a/libs/flake/KoOdfWorkaround.h b/libs/flake/KoOdfWorkaround.h index 684828c37d..038f4cf8c9 100644 --- a/libs/flake/KoOdfWorkaround.h +++ b/libs/flake/KoOdfWorkaround.h @@ -1,162 +1,162 @@ /* This file is part of the KDE project Copyright (C) 2009 Thorsten Zachmann Copyright (C) 2011 Jan Hambrecht Copyright (C) 2011 Lukáš Tvrdý 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 KOODFWORKAROUND_H #define KOODFWORKAROUND_H #include "kritaflake_export.h" #include "KoTextShapeDataBase.h" #include #include -class KoXmlElement; +#include class KoShape; class KoShapeLoadingContext; class QPen; class QColor; class QString; class KoColorBackground; /** * This class should contain all workarounds to correct problems with different ODF * implementations. If you need to access application specific things please create a * new namespace in the application you need it in * All calls to methods of this class should be wrapped into ifndefs like e.g. * * @code * #ifndef NWORKAROUND_ODF_BUGS * KoOdfWorkaround::fixPenWidth(pen, context); * #endif * @endcode */ namespace KoOdfWorkaround { /** * OpenOffice handles a line with the width of 0 as a cosmetic line but in svg it makes the line invisible. * To show it in calligra use a very small line width. However this is not a cosmetic line. */ KRITAFLAKE_EXPORT void fixPenWidth(QPen &pen, KoShapeLoadingContext &context); /** * OpenOffice < 3.0 does not store the draw:enhanced-path for draw:type="ellipse" * Add the path needed for the ellipse */ KRITAFLAKE_EXPORT void fixEnhancedPath(QString &path, const KoXmlElement &element, KoShapeLoadingContext &context); /** * OpenOffice interchanges the position coordinates for polar handles. * According to the specification the first coordinate is the angle, the * second coordinates is the radius. OpenOffice does it the other way around. */ KRITAFLAKE_EXPORT void fixEnhancedPathPolarHandlePosition(QString &position, const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixMissingStroke(QPen &pen, const KoXmlElement &element, KoShapeLoadingContext &context, const KoShape *shape = 0); KRITAFLAKE_EXPORT QColor fixMissingFillColor(const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixMissingStyle_DisplayLabel(const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT QSharedPointer fixBackgroundColor(const KoShape *shape, KoShapeLoadingContext &context); /** * Old versions of ooimpress does not set the placeholder for shapes that should have it set * See open office issue http://www.openoffice.org/issues/show_bug.cgi?id=96406 * And kde bug https://bugs.kde.org/show_bug.cgi?id=185354 */ KRITAFLAKE_EXPORT void setFixPresentationPlaceholder(bool fix, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixPresentationPlaceholder(); KRITAFLAKE_EXPORT void fixPresentationPlaceholder(KoShape *shape); /** * OpenOffice and LibreOffice save gluepoint positions wrong when no align is specified. * According to the specification for the above situation, the position should be saved * as percent values relative to the shapes center point. OpenOffice seems to write * these percent values converted to length units, where the millimeter value corresponds * to the correct percent value (i.e. -5cm = -50mm = -50%). */ KRITAFLAKE_EXPORT void fixGluePointPosition(QString &positionString, KoShapeLoadingContext &context); /** * OpenOffice and LibreOffice does not conform to the specification about default value * of the svg:fill-rule. If this attribute is missing, according the spec, the initial * value is nonzero, but OOo uses evenodd. Because we are conform to the spec, we need * to set what OOo display. * See http://www.w3.org/TR/SVG/painting.html#FillRuleProperty */ KRITAFLAKE_EXPORT void fixMissingFillRule(Qt::FillRule &fillRule, KoShapeLoadingContext &context); /** - * OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to + * OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to * small the text will not fit and it needs to be adjusted during the first layout. * This methods returns true if we need to adjust the layout. The adjusting is handled at a different place. */ KRITAFLAKE_EXPORT bool fixAutoGrow(KoTextShapeDataBase::ResizeMethod method, KoShapeLoadingContext &context); /** * OpenOffice and LibreOffice do not set the svg:width, svg:height, svg:x and svg:y correctly when saving * parts of draw:ellipses or draw:circle * This method returns true when the width, height, x and y is given for the full circle */ KRITAFLAKE_EXPORT bool fixEllipse(const QString &kind, KoShapeLoadingContext &context); /** * Calligra did use the bad strings "Formula.hidden" and "protected Formula.hidden" as values * for style:cell-protect, instead of "formula-hidden" and "protected formula-hidden". * This method fixes the bad strings to the correct ones. */ KRITAFLAKE_EXPORT void fixBadFormulaHiddenForStyleCellProtect(QString &value); /** * Calligra used to store text:time-value with a "0-00-00T" prefix * This method removes that prefix. */ KRITAFLAKE_EXPORT void fixBadDateForTextTime(QString &value); /** * OpenOffice.org used to write the "rect(...)" value for fo:clip without * separating the 4 offset values by commas. * This method changes the string with the offset values to have commas as separators. */ KRITAFLAKE_EXPORT void fixClipRectOffsetValuesString(QString &offsetValuesString); /** * LibreOffice used to write text:style-name attribute for table:table-template element, * which is not a valid attribute for the element. */ KRITAFLAKE_EXPORT QString fixTableTemplateName(const KoXmlElement &e); /** * LibreOffice used to write text:style-name attribute for * table:first-row, table:last-row, table:first-column, * table:last-column, table:odd-rows, table:odd-columns, * table:body elements, which is not a valid attribute for the element. */ KRITAFLAKE_EXPORT QString fixTableTemplateCellStyleName(const KoXmlElement &e); /** * LibreOffice used to have a bug with handling of z command in svg path. * This resulted in broken marker path used (and copied also to Calligra). * This methods substitutes known old marker paths with the latest (fixed) * path variant. */ KRITAFLAKE_EXPORT void fixMarkerPath(QString &path); } #endif /* KOODFWORKAROUND_H */ diff --git a/libs/flake/KoShapeAnchor.h b/libs/flake/KoShapeAnchor.h index 8a3e700416..06b3465e7e 100644 --- a/libs/flake/KoShapeAnchor.h +++ b/libs/flake/KoShapeAnchor.h @@ -1,262 +1,262 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2011 Matus Hanzes * Copyright (C) 2013 C. Boemann * * 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 KOSHAPEANCHOR_H #define KOSHAPEANCHOR_H #include "kritaflake_export.h" class KoShape; -class KoXmlElement; +#include class KoShapeLoadingContext; class KoShapeSavingContext; class KoShapeAnchorPrivate; class QTextDocument; class QPointF; class QString; /** * This class is the object that explains how a shape is anchored to something. * * The anchored shape will be positioned (in supporting applications) based on the properties * defined in this class. * * This class can be used in three different ways: * -page anchor * -as-char * -char, paragraph anchor * * If it's a page anchor it just provide the info about how the shape relates to a page with a specific * page number. * * For the other types of anchoring it has to have a TextLocation in a QTextDocument. This TextLocation * can either be an inline character (type as-char) or a position (type char or paragraph) The * KoShapeAnchor and TextLocation connects the anchored-shape to the text flow so the anchored shape * can be repositioned on the canvas if new text is inserted or removed before the anchor character. * * For as-char, char and paragraph use cases: * @see KoAnchorInlineObject * @see KoAnchorTextRange * which are both implemented as subclasses of TextLocation * * The position of the shape relative to the anchor is called the offset. It's loaded by loadOdf(). * @see PlacementStrategy for more information about the layout of anchors/shapes. */ class KRITAFLAKE_EXPORT KoShapeAnchor { public: /** * This class is an interface that positions the shape linked to text anchor */ class PlacementStrategy { public: PlacementStrategy(){}; virtual ~PlacementStrategy(){}; /** * Reparent the anchored shape to not have a parent shape container (and model) * */ virtual void detachFromModel() = 0; /** * Reparent the anchored shape under an appropriate shape container (and model) * * If needed, it changes the parent KoShapeContainerModel and KoShapeContainer of the anchored * shape. */ virtual void updateContainerModel() = 0; }; class TextLocation { public: TextLocation(){}; virtual ~TextLocation(){}; virtual const QTextDocument *document() const = 0; virtual int position() const = 0; }; enum HorizontalPos { HCenter, HFromInside, HFromLeft, HInside, HLeft, HOutside, HRight }; enum HorizontalRel { //NOTE: update KWAnchoringProperties if you change this HChar, HPage, HPageContent, HPageStartMargin, HPageEndMargin, HFrame, HFrameContent, HFrameEndMargin, HFrameStartMargin, HParagraph, HParagraphContent, HParagraphEndMargin, HParagraphStartMargin }; enum VerticalPos { VBelow, VBottom, VFromTop, VMiddle, VTop }; enum VerticalRel { //NOTE: update KWAnchoringProperties if you change this VBaseline, VChar, VFrame, VFrameContent, VLine, VPage, VPageContent, VParagraph, VParagraphContent, VText }; enum AnchorType { AnchorAsCharacter, AnchorToCharacter, AnchorParagraph, AnchorPage }; /** * Constructor for an in-place anchor. * @param shape the anchored shape that this anchor links to. */ explicit KoShapeAnchor(KoShape *shape); virtual ~KoShapeAnchor(); /** * Return the shape that is linked to from the text anchor. */ KoShape *shape() const; /** * Returns the type of the anchor. * * The text:anchor-type attribute specifies how a frame is bound to a * text document. The anchor position is the point at which a frame is * bound to a text document. The defined values for the text:anchor-type * attribute are; * * - as-char * There is no anchor position. The drawing shape behaves like a * character. * - char * The character after the drawing shape element. * - frame * The parent text box that the current drawing shape element is * contained in. * FIXME we don't support type frame * - page * The page that has the same physical page number as the value of the * text:anchor-page-number attribute that is attached to the drawing * shape element. * - paragraph * The paragraph that the current drawing shape element is contained in. */ AnchorType anchorType() const; /** * Set how the anchor behaves */ void setAnchorType(AnchorType type); /// set the current vertical-pos void setHorizontalPos(HorizontalPos); /// return the current vertical-pos HorizontalPos horizontalPos() const; /// set the current vertical-rel void setHorizontalRel(HorizontalRel); /// return the current vertical-rel HorizontalRel horizontalRel() const; /// set the current horizontal-pos void setVerticalPos(VerticalPos); /// return the current horizontal-pos VerticalPos verticalPos() const; /// set the current horizontal-rel void setVerticalRel(VerticalRel); /// return the current horizontal-rel VerticalRel verticalRel() const; /// return the wrap influence on position QString wrapInfluenceOnPosition() const; /// return if flow-with-text (odf attribute) bool flowWithText() const; /// return the page number of the shape (valid with page anchoring, -1 indicates auto). int pageNumber() const; /// return the offset of the shape from the anchor. const QPointF &offset() const; /// set the new offset of the shape. Causes a new layout soon. void setOffset(const QPointF &offset); /// Load the additional attributes. /// This will also make the shape invisible so it doesn't mess up any layout /// before it's ready to be placed where it belongs /// The textlayout should make it visible again bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context); /// Save the additional attributes. void saveOdf(KoShapeSavingContext &context) const; /// Get extra data structure that is what is actually inside a text document TextLocation *textLocation() const; /// Set extra data structure that is what is actually inside a text document /// We do NOT take ownership (may change in the future) void setTextLocation(TextLocation *textLocation); /// Get placement strategy which is used to position shape linked to text anchor PlacementStrategy *placementStrategy() const; /// Set placement strategy which is used to position shape linked to text anchor /// We take owner ship and will make sure the strategy is deleted void setPlacementStrategy(PlacementStrategy *placementStrategy); private: class Private; Private * const d; }; #endif diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp index d277c9366b..0c67c96b15 100644 --- a/libs/flake/KoShapeController.cpp +++ b/libs/flake/KoShapeController.cpp @@ -1,212 +1,212 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeController.h" #include "KoShapeBasedDocumentBase.h" #include "KoShapeRegistry.h" #include "KoDocumentResourceManager.h" #include "KoShapeManager.h" #include "KoShapeLayer.h" #include "KoSelection.h" #include "commands/KoShapeCreateCommand.h" #include "commands/KoShapeDeleteCommand.h" #include "commands/KoShapeConnectionChangeCommand.h" #include "KoCanvasBase.h" #include "KoShapeConfigWidgetBase.h" #include "KoShapeFactoryBase.h" #include "KoShape.h" #include "KoConnectionShape.h" #include #include #include #include class KoShapeController::Private { public: Private() : canvas(0), shapeBasedDocument(0) { } KoCanvasBase *canvas; KoShapeBasedDocumentBase *shapeBasedDocument; KUndo2Command* addShape(KoShape *shape, bool showDialog, KUndo2Command *parent) { if (canvas) { - if (showDialog) { + if (showDialog && !shape->shapeId().isEmpty()) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId()); Q_ASSERT(factory); int z = 0; Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) z = qMax(z, sh->zIndex()); shape->setZIndex(z + 1); // show config dialog. KPageDialog *dialog = new KPageDialog(canvas->canvasWidget()); dialog->setWindowTitle(i18n("%1 Options", factory->name())); int pageCount = 0; QList widgets; Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) { if (! panel->showOnShapeCreate()) continue; panel->open(shape); panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept())); widgets.append(panel); panel->setResourceManager(canvas->resourceManager()); panel->setUnit(canvas->unit()); QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle(); dialog->addPage(panel, title); pageCount ++; } if (pageCount > 0) { if (pageCount > 1) dialog->setFaceType(KPageDialog::Tabbed); if (dialog->exec() != KPageDialog::Accepted) { delete dialog; return 0; } Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets) widget->save(); } delete dialog; } } return addShapesDirect({shape}, parent); } KUndo2Command* addShapesDirect(const QList shapes, KUndo2Command *parent) { return new KoShapeCreateCommand(shapeBasedDocument, shapes, parent); } void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) { foreach (KoShape *dependee, shape->dependees()) { KoConnectionShape *connection = dynamic_cast(dependee); if (connection) { if (shape == connection->firstShape()) { new KoShapeConnectionChangeCommand(connection, KoConnectionShape::StartHandle, shape, connection->firstConnectionId(), 0, -1, parentCmd); } else if (shape == connection->secondShape()) { new KoShapeConnectionChangeCommand(connection, KoConnectionShape::EndHandle, shape, connection->secondConnectionId(), 0, -1, parentCmd); } } } } }; KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument) : d(new Private()) { d->canvas = canvas; d->shapeBasedDocument = shapeBasedDocument; if (shapeBasedDocument) { shapeBasedDocument->resourceManager()->setShapeController(this); } } KoShapeController::~KoShapeController() { delete d; } void KoShapeController::reset() { d->canvas = 0; d->shapeBasedDocument = 0; } KUndo2Command* KoShapeController::addShape(KoShape *shape, KUndo2Command *parent) { return d->addShape(shape, true, parent); } KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KUndo2Command *parent) { return d->addShapesDirect({shape}, parent); } KUndo2Command *KoShapeController::addShapesDirect(const QList shapes, KUndo2Command *parent) { return d->addShapesDirect(shapes, parent); } KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent) { KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shape, parent); QList shapes; shapes.append(shape); d->shapeBasedDocument->shapesRemoved(shapes, cmd); // detach shape from any attached connection shapes d->handleAttachedConnections(shape, cmd); return cmd; } KUndo2Command* KoShapeController::removeShapes(const QList &shapes, KUndo2Command *parent) { KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shapes, parent); d->shapeBasedDocument->shapesRemoved(shapes, cmd); foreach (KoShape *shape, shapes) { d->handleAttachedConnections(shape, cmd); } return cmd; } void KoShapeController::setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument) { d->shapeBasedDocument = shapeBasedDocument; } QRectF KoShapeController::documentRectInPixels() const { return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPixels() : QRectF(0,0,1920,1080); } qreal KoShapeController::pixelsPerInch() const { return d->shapeBasedDocument ? d->shapeBasedDocument->pixelsPerInch() : 72.0; } QRectF KoShapeController::documentRect() const { return d->shapeBasedDocument ? d->shapeBasedDocument->documentRect() : documentRectInPixels(); } KoDocumentResourceManager *KoShapeController::resourceManager() const { if (!d->shapeBasedDocument) return 0; return d->shapeBasedDocument->resourceManager(); } KoShapeBasedDocumentBase *KoShapeController::documentBase() const { return d->shapeBasedDocument; } diff --git a/libs/flake/KoShapeSavingContext.cpp b/libs/flake/KoShapeSavingContext.cpp index fa8a8f4f3b..caa3122743 100644 --- a/libs/flake/KoShapeSavingContext.cpp +++ b/libs/flake/KoShapeSavingContext.cpp @@ -1,346 +1,346 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007-2009, 2011 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeSavingContext.h" #include "KoDataCenterBase.h" #include "KoShapeLayer.h" #include "KoImageData.h" #include "KoMarker.h" #include #include #include #include #include #include #include #include #include class KoShapeSavingContextPrivate { public: KoShapeSavingContextPrivate(KoXmlWriter&, KoGenStyles&, KoEmbeddedDocumentSaver&); ~KoShapeSavingContextPrivate(); KoXmlWriter *xmlWriter; KoShapeSavingContext::ShapeSavingOptions savingOptions; QList layers; QSet dataCenters; QMap sharedData; QMap imageNames; int imageId; QMap images; QHash shapeOffsets; QMap markerRefs; KoGenStyles& mainStyles; KoEmbeddedDocumentSaver& embeddedSaver; QMap references; QMap referenceCounters; QMap > prefixedReferences; }; KoShapeSavingContextPrivate::KoShapeSavingContextPrivate(KoXmlWriter &w, KoGenStyles &s, KoEmbeddedDocumentSaver &e) : xmlWriter(&w), savingOptions(0), imageId(0), mainStyles(s), embeddedSaver(e) { } KoShapeSavingContextPrivate::~KoShapeSavingContextPrivate() { Q_FOREACH (KoSharedSavingData * data, sharedData) { delete data; } } KoShapeSavingContext::KoShapeSavingContext(KoXmlWriter &xmlWriter, KoGenStyles &mainStyles, KoEmbeddedDocumentSaver &embeddedSaver) : d(new KoShapeSavingContextPrivate(xmlWriter, mainStyles, embeddedSaver)) { // by default allow saving of draw:id + xml:id addOption(KoShapeSavingContext::DrawId); } KoShapeSavingContext::~KoShapeSavingContext() { delete d; } KoXmlWriter & KoShapeSavingContext::xmlWriter() { return *d->xmlWriter; } void KoShapeSavingContext::setXmlWriter(KoXmlWriter &xmlWriter) { d->xmlWriter = &xmlWriter; } KoGenStyles & KoShapeSavingContext::mainStyles() { return d->mainStyles; } KoEmbeddedDocumentSaver &KoShapeSavingContext::embeddedSaver() { return d->embeddedSaver; } bool KoShapeSavingContext::isSet(ShapeSavingOption option) const { return d->savingOptions & option; } void KoShapeSavingContext::setOptions(ShapeSavingOptions options) { d->savingOptions = options; } KoShapeSavingContext::ShapeSavingOptions KoShapeSavingContext::options() const { return d->savingOptions; } void KoShapeSavingContext::addOption(ShapeSavingOption option) { d->savingOptions = d->savingOptions | option; } void KoShapeSavingContext::removeOption(ShapeSavingOption option) { if (isSet(option)) d->savingOptions = d->savingOptions ^ option; // xor to remove it. } KoElementReference KoShapeSavingContext::xmlid(const void *referent, const QString& prefix, KoElementReference::GenerationOption counter) { Q_ASSERT(counter == KoElementReference::UUID || (counter == KoElementReference::Counter && !prefix.isEmpty())); if (d->references.contains(referent)) { return d->references[referent]; } KoElementReference ref; if (counter == KoElementReference::Counter) { int referenceCounter = d->referenceCounters[prefix]; referenceCounter++; ref = KoElementReference(prefix, referenceCounter); d->references.insert(referent, ref); d->referenceCounters[prefix] = referenceCounter; } else { if (!prefix.isEmpty()) { ref = KoElementReference(prefix); d->references.insert(referent, ref); } else { d->references.insert(referent, ref); } } if (!prefix.isNull()) { d->prefixedReferences[prefix].append(referent); } return ref; } KoElementReference KoShapeSavingContext::existingXmlid(const void *referent) { if (d->references.contains(referent)) { return d->references[referent]; } else { KoElementReference ref; ref.invalidate(); return ref; } } void KoShapeSavingContext::clearXmlIds(const QString &prefix) { if (d->prefixedReferences.contains(prefix)) { Q_FOREACH (const void* ptr, d->prefixedReferences[prefix]) { d->references.remove(ptr); } d->prefixedReferences.remove(prefix); } if (d->referenceCounters.contains(prefix)) { d->referenceCounters[prefix] = 0; } } void KoShapeSavingContext::addLayerForSaving(const KoShapeLayer *layer) { if (layer && ! d->layers.contains(layer)) d->layers.append(layer); } void KoShapeSavingContext::saveLayerSet(KoXmlWriter &xmlWriter) const { xmlWriter.startElement("draw:layer-set"); Q_FOREACH (const KoShapeLayer * layer, d->layers) { xmlWriter.startElement("draw:layer"); xmlWriter.addAttribute("draw:name", layer->name()); if (layer->isGeometryProtected()) xmlWriter.addAttribute("draw:protected", "true"); if (! layer->isVisible()) xmlWriter.addAttribute("draw:display", "none"); xmlWriter.endElement(); // draw:layer } xmlWriter.endElement(); // draw:layer-set } void KoShapeSavingContext::clearLayers() { d->layers.clear(); } QString KoShapeSavingContext::imageHref(const KoImageData *image) { QMap::iterator it(d->imageNames.find(image->key())); if (it == d->imageNames.end()) { QString suffix = image->suffix(); if (suffix.isEmpty()) { it = d->imageNames.insert(image->key(), QString("Pictures/image%1").arg(++d->imageId)); } else { it = d->imageNames.insert(image->key(), QString("Pictures/image%1.%2").arg(++d->imageId).arg(suffix)); } } return it.value(); } QString KoShapeSavingContext::imageHref(const QImage &image) { // TODO this can be optimized to recognize images which have the same content // Also this can use quite a lot of memeory as the qimage are all kept until // they are saved to the store in memory QString href = QString("Pictures/image%1.png").arg(++d->imageId); d->images.insert(href, image); return href; } QMap KoShapeSavingContext::imagesToSave() { return d->imageNames; } -QString KoShapeSavingContext::markerRef(const KoMarker *marker) +QString KoShapeSavingContext::markerRef(const KoMarker */*marker*/) { // QMap::iterator it = d->markerRefs.find(marker); // if (it == d->markerRefs.end()) { // it = d->markerRefs.insert(marker, marker->saveOdf(*this)); // } // return it.value(); return QString(); } void KoShapeSavingContext::addDataCenter(KoDataCenterBase * dataCenter) { if (dataCenter) { d->dataCenters.insert(dataCenter); } } bool KoShapeSavingContext::saveDataCenter(KoStore *store, KoXmlWriter* manifestWriter) { bool ok = true; Q_FOREACH (KoDataCenterBase *dataCenter, d->dataCenters) { ok = ok && dataCenter->completeSaving(store, manifestWriter, this); //debugFlake << "ok" << ok; } // Save images for (QMap::iterator it(d->images.begin()); it != d->images.end(); ++it) { if (store->open(it.key())) { KoStoreDevice device(store); ok = ok && it.value().save(&device, "PNG"); store->close(); // TODO error handling if (ok) { const QString mimetype = KisMimeDatabase::mimeTypeForFile(it.key()); manifestWriter->addManifestEntry(it.key(), mimetype); } else { warnFlake << "saving image failed"; } } else { ok = false; warnFlake << "saving image failed: open store failed"; } } return ok; } void KoShapeSavingContext::addSharedData(const QString &id, KoSharedSavingData * data) { QMap::iterator it(d->sharedData.find(id)); // data will not be overwritten if (it == d->sharedData.end()) { d->sharedData.insert(id, data); } else { warnFlake << "The id" << id << "is already registered. Data not inserted"; Q_ASSERT(it == d->sharedData.end()); } } KoSharedSavingData * KoShapeSavingContext::sharedData(const QString &id) const { KoSharedSavingData * data = 0; QMap::const_iterator it(d->sharedData.constFind(id)); if (it != d->sharedData.constEnd()) { data = it.value(); } return data; } void KoShapeSavingContext::addShapeOffset(const KoShape *shape, const QTransform &m) { d->shapeOffsets.insert(shape, m); } void KoShapeSavingContext::removeShapeOffset(const KoShape *shape) { d->shapeOffsets.remove(shape); } QTransform KoShapeSavingContext::shapeOffset(const KoShape *shape) const { return d->shapeOffsets.value(shape, QTransform()); } diff --git a/libs/flake/KoSvgPaste.cpp b/libs/flake/KoSvgPaste.cpp index 205cca0285..6da3f9abb7 100644 --- a/libs/flake/KoSvgPaste.cpp +++ b/libs/flake/KoSvgPaste.cpp @@ -1,72 +1,82 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoSvgPaste.h" #include #include #include #include #include #include #include #include -KoSvgPaste::KoSvgPaste() -{ -} - -bool KoSvgPaste::hasShapes() const +bool KoSvgPaste::hasShapes() { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); return mimeData && mimeData->hasFormat("image/svg+xml"); } -QList KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize) const +QList KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize) { QList shapes; const QMimeData *mimeData = QApplication::clipboard()->mimeData(); if (!mimeData) return shapes; QByteArray data = mimeData->data("image/svg+xml"); - if (data.isEmpty()) return shapes; + if (data.isEmpty()) { + return shapes; + } + + return fetchShapesFromData(data, viewportInPx, resolutionPPI, fragmentSize); + +} + +QList KoSvgPaste::fetchShapesFromData(const QByteArray &data, const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize) +{ + QList shapes; + + if (data.isEmpty()) { + return shapes; + } KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn = 0; const bool documentValid = doc.setContent(data, false, &errorMsg, &errorLine, &errorColumn); if (!documentValid) { - errorFlake << "Failed to process an SVG file at" + qWarning() << "Failed to process an SVG file at" << errorLine << ":" << errorColumn << "->" << errorMsg; return shapes; } KoDocumentResourceManager resourceManager; SvgParser parser(&resourceManager); parser.setResolution(viewportInPx, resolutionPPI); shapes = parser.parseSvg(doc.documentElement(), fragmentSize); return shapes; } diff --git a/libs/flake/KoSvgPaste.h b/libs/flake/KoSvgPaste.h index 98400cf6e1..6c44b9e3f8 100644 --- a/libs/flake/KoSvgPaste.h +++ b/libs/flake/KoSvgPaste.h @@ -1,38 +1,39 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOSVGPASTE_H #define KOSVGPASTE_H #include "kritaflake_export.h" #include class KoShape; class QRectF; class QSizeF; +class QByteArray; class KRITAFLAKE_EXPORT KoSvgPaste { public: - KoSvgPaste(); + static bool hasShapes(); + static QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0); + static QList fetchShapesFromData(const QByteArray &data, const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0); - bool hasShapes() const; - QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const; }; #endif // KOSVGPASTE_H diff --git a/libs/flake/KoTextShapeDataBase.h b/libs/flake/KoTextShapeDataBase.h index 14236e49fc..a5aab94bc1 100644 --- a/libs/flake/KoTextShapeDataBase.h +++ b/libs/flake/KoTextShapeDataBase.h @@ -1,145 +1,145 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 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 KOTEXTSHAPEDATABASE_H #define KOTEXTSHAPEDATABASE_H #include "kritaflake_export.h" #include "KoShapeUserData.h" class KoTextShapeDataBasePrivate; -class KoXmlElement; +#include class KoShapeLoadingContext; class KoShapeSavingContext; class KoGenStyle; struct KoInsets; class QTextDocument; /** * \internal */ class KRITAFLAKE_EXPORT KoTextShapeDataBase : public KoShapeUserData { Q_OBJECT public: /// constructor KoTextShapeDataBase(); ~KoTextShapeDataBase() override; /// return the document QTextDocument *document() const; /** * Set the margins that will make the shapes text area smaller. * The shape that owns this textShapeData object will layout text in an area * confined by the shape size made smaller by the margins set here. * @param margins the margins that shrink the text area. */ void setShapeMargins(const KoInsets &margins); /** * returns the currently set margins for the shape. */ KoInsets shapeMargins() const; /** * Load the text from ODF. */ virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; /** * Save the text to ODF. */ virtual void saveOdf(KoShapeSavingContext &context, int from = 0, int to = -1) const = 0; /** * Load the style of the element * * This method is used to load the style in case the TextShape is used as TOS. In this case * the paragraph style of the shape e.g. a custom-shape needs to be applied before we load the * text so all looks as it should look. */ virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; /** * Save the style of the element * * This method save the style in case the TextShape is used as TOS. In this case the paragraph * style of the shape e.g. a custom-shape needs to be saved with the style of the shape. */ virtual void saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const = 0; /** Sets the vertical alignment of all the text inside the shape. */ void setVerticalAlignment(Qt::Alignment alignment); /** Returns the vertical alignment of all the text in the shape */ Qt::Alignment verticalAlignment() const; /** * Enum to describe the text document's automatic resizing behaviour. */ enum ResizeMethod { /// Resize the shape to fit the content. This makes sure that the text shape takes op /// only as much space as absolutely necessary to fit the entire text into its boundaries. AutoResize, /// Specifies whether or not to automatically increase the width of the drawing object /// if text is added to fit the entire width of the text into its boundaries. /// Compared to AutoResize above this only applied to the width whereas the height is /// not resized. Also this only grows but does not shrink again if text is removed again. AutoGrowWidth, /// Specifies whether or not to automatically increase the height of the drawing object /// if text is added to fit the entire height of the text into its boundaries. AutoGrowHeight, /// This combines the AutoGrowWidth and AutoGrowHeight and automatically increase width /// and height to fit the entire text into its boundaries. AutoGrowWidthAndHeight, /// Shrink the content displayed within the shape to match into the shape's boundaries. This /// will scale the content down as needed to display the whole document. ShrinkToFitResize, /// Deactivates auto-resizing. This is the default resizing method. NoResize }; /** * Specifies how the document should be resized upon a change in the document. * * If auto-resizing is turned on, text will not be wrapped unless enforced by e.g. a newline. * * By default, NoResize is set. */ void setResizeMethod(ResizeMethod method); /** * Returns the auto-resizing mode. By default, this is NoResize. * * @see setResizeMethod */ ResizeMethod resizeMethod() const; protected: /// constructor KoTextShapeDataBase(KoTextShapeDataBasePrivate *); KoTextShapeDataBasePrivate *d_ptr; private: Q_DECLARE_PRIVATE(KoTextShapeDataBase) }; #endif diff --git a/libs/flake/KoUnavailShape.cpp b/libs/flake/KoUnavailShape.cpp index 240a9d2ba1..1c85d0f7b2 100644 --- a/libs/flake/KoUnavailShape.cpp +++ b/libs/flake/KoUnavailShape.cpp @@ -1,656 +1,628 @@ /* This file is part of the KDE project * * Copyright (C) 2010-2011 Inge Wallin * * 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. */ // Own #include "KoUnavailShape.h" // Qt #include #include #include #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "SimpleShapeContainerModel.h" #include "KoShapeBackground.h" #include // The XML of a frame looks something like this: -// +// // 1. // 2. // 3. // 4. // // or -// +// // 1. -// 2. ...inline xml here... +// 2. ...inline xml here... // 3. // 4. // // We define each Xml statement on lines 2 and 3 above as an "object". // (Strictly only the first child element is an object in the ODF sense, // but we have to have some terminology here.) -// +// // In an ODF frame, only the first line, i.e. the first object // contains the real contents. All the rest of the objects are used / // shown if we cannot handle the first one. The most common cases are // that there is only one object inside the frame OR that there are 2 // and the 2nd is a picture. // // Sometimes, e.g. in the case of an embedded document, the reference // points not to a file but to a directory structure inside the ODF -// store. +// store. // // When we load and save in the UnavailShape, we have to be general // enough to cover all possible cases of references and inline XML, // embedded files and embedded directory structures. // // We also have to be careful because we cannot reuse the object names // that are in the original files when saving. Instead we need to // create new object names because the ones that were used in the // original file may already be used by other embedded files/objects // that are saved by other shapes. // // FIXME: There should only be ONE place where new object / file names // are generated, not 2(?) like there are now: // KoEmbeddedDocumentSaver and the KoImageCollection. // // An ObjectEntry is used to store information about objects in the // frame, as defined above. struct ObjectEntry { QByteArray objectXmlContents; // the XML tree in the object QString objectName; // object name in the frame without "./" // This is extracted from objectXmlContents. bool isDir; KoOdfManifestEntry *manifestEntry; // manifest entry for the above. }; // A FileEntry is used to store information about embedded files // inside (i.e. referred to by) an object. struct FileEntry { QString path; // Normalized filename, i.e. without "./". QString mimeType; bool isDir; QByteArray contents; }; class KoUnavailShape::Private { public: Private(KoUnavailShape* qq); ~Private(); void draw(QPainter &painter) const; void drawNull(QPainter &painter) const; void storeObjects(const KoXmlElement &element); void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces); void storeFile(const QString &filename, KoShapeLoadingContext &context); QByteArray loadFile(const QString &filename, KoShapeLoadingContext &context); // Objects inside the frame. For each file, we store: // - The XML code for the object // - Any embedded files (names, contents) that are referenced by xlink:href // - Whether they are directories, i.e. if they contain a file tree and not just one file. // - The manifest entries QList objectEntries; // Embedded files QList embeddedFiles; // List of embedded files. // Some cached values. QPixmap questionMark; QPixmap pixmapPreview; QSvgRenderer *scalablePreview; KoUnavailShape* q; }; KoUnavailShape::Private::Private(KoUnavailShape* qq) : scalablePreview(new QSvgRenderer()) , q(qq) { // Get the question mark "icon". questionMark.load(":/questionmark.png"); } KoUnavailShape::Private::~Private() { qDeleteAll(objectEntries); qDeleteAll(embeddedFiles); // It's a QObject, but we haven't parented it. delete(scalablePreview); } // ---------------------------------------------------------------- // The main class KoUnavailShape::KoUnavailShape() : KoFrameShape( "", "" ) , KoShapeContainer(new SimpleShapeContainerModel()) , d(new Private(this)) { setShapeId(KoUnavailShape_SHAPEID); // Default size of the shape. KoShape::setSize( QSizeF( CM_TO_POINT( 5 ), CM_TO_POINT( 3 ) ) ); } KoUnavailShape::~KoUnavailShape() { delete d; } void KoUnavailShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { applyConversion(painter, converter); // If the frame is empty, just draw a background. debugFlake << "Number of objects:" << d->objectEntries.size(); if (d->objectEntries.isEmpty()) { // But... only try to draw the background if there's one such if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } } else { if(shapes().isEmpty()) { d->draw(painter); } } } void KoUnavailShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KoUnavailShape::Private::draw(QPainter &painter) const { painter.save(); painter.setRenderHint(QPainter::Antialiasing); // Run through the previews in order of preference. Draw a placeholder // questionmark if there is no preview available for rendering. if (scalablePreview->isValid()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); scalablePreview->render(&painter, bounds); } else if (!pixmapPreview.isNull()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.drawPixmap(bounds, pixmapPreview); } else if (q->shapes().isEmpty()) { // Draw a nice question mark with a frame around it if there // is no other preview image. If there is a contained image // shape, we don't need to draw anything. // Get the question mark "icon". // FIXME: We should be able to use d->questionMark here. QPixmap questionMark; questionMark.load(":/questionmark.png"); // The size of the image is: // - the size of the shape if shapesize < 2cm // - 2 cm if 2cm <= shapesize <= 8cm // - shapesize / 4 if shapesize > 8cm qreal width = q->size().width(); qreal height = q->size().height(); qreal picSize = CM_TO_POINT(2); // Default size is 2 cm. if (width < CM_TO_POINT(2) || height < CM_TO_POINT(2)) picSize = qMin(width, height); else if (width > CM_TO_POINT(8) && height > CM_TO_POINT(8)) picSize = qMin(width, height) / qreal(4.0); painter.drawPixmap((width - picSize) / qreal(2.0), (height - picSize) / qreal(2.0), picSize, picSize, questionMark); // Draw a gray rectangle around the shape. painter.setPen(QPen(QColor(172, 196, 206), 0)); painter.drawRect(QRectF(QPointF(0,0), q->size())); } painter.restore(); } void KoUnavailShape::Private::drawNull(QPainter &painter) const { QRectF rect(QPointF(0,0), q->size()); painter.save(); // Draw a simple cross in a rectangle just to indicate that there is something here. painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.bottomLeft(), rect.topRight()); painter.restore(); } // ---------------------------------------------------------------- // Loading and Saving void KoUnavailShape::saveOdf(KoShapeSavingContext & context) const { debugFlake << "START SAVING ##################################################"; KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver(); KoXmlWriter &writer = context.xmlWriter(); writer.startElement("draw:frame"); // See also loadOdf() in loadOdfAttributes. saveOdfAttributes( context, OdfAllAttributes ); // Write the stored XML to the file, but don't reuse object names. int lap = 0; QString newName; foreach (const ObjectEntry *object, d->objectEntries) { QByteArray xmlArray(object->objectXmlContents); QString objectName(object->objectName); // Possibly empty. KoOdfManifestEntry *manifestEntry(object->manifestEntry); // Create a name for this object. If this is not the first // object, i.e. a replacement object (most likely a picture), // then reuse the name but put it in ReplacementObjects. if (++lap == 1) { // The first lap in the loop is the actual object. All // other laps are replacement objects. newName = fileSaver.getFilename("Object "); } else if (lap == 2) { newName = "ObjectReplacements/" + newName; } else // FIXME: what should replacement 2 and onwards be called? newName = newName + "_"; // If there was a previous object name, replace it with the new one. if (!objectName.isEmpty() && manifestEntry) { // FIXME: We must make a copy of the byte array here because // otherwise we won't be able to save > 1 time. xmlArray.replace(objectName.toLatin1(), newName.toLatin1()); } writer.addCompleteElement(xmlArray.data()); // If the objectName is empty, this may be inline XML. // If so, we are done now. if (objectName.isEmpty() || !manifestEntry) { continue; } // Save embedded files for this object. foreach (FileEntry *entry, d->embeddedFiles) { QString fileName(entry->path); // If we found a file for this object, we need to write it // but with the new object name instead of the old one. if (!fileName.startsWith(objectName)) continue; debugFlake << "Object name: " << objectName << "newName: " << newName << "filename: " << fileName << "isDir: " << entry->isDir; fileName.replace(objectName, newName); fileName.prepend("./"); debugFlake << "New filename: " << fileName; // FIXME: Check if we need special treatment of directories. fileSaver.saveFile(fileName, entry->mimeType.toLatin1(), entry->contents); } // Write the manifest entry for the object itself. If it's a // file, the manifest is already written by saveFile, so skip // it here. if (object->isDir) { fileSaver.saveManifestEntry(newName + '/', manifestEntry->mediaType(), manifestEntry->version()); } } writer.endElement(); // draw:frame } bool KoUnavailShape::loadOdf(const KoXmlElement &frameElement, KoShapeLoadingContext &context) { debugFlake << "START LOADING ##################################################"; //debugFlake << "Loading ODF frame in the KoUnavailShape. Element = " // << frameElement.tagName(); loadOdfAttributes(frameElement, context, OdfAllAttributes); // NOTE: We cannot use loadOdfFrame() because we want to save all // the things inside the frame, not just one of them, like // loadOdfFrame() provides. // Get the manifest. QList manifest = context.odfLoadingContext().manifestEntries(); #if 0 // Enable to show all manifest entries. debugFlake << "MANIFEST: "; foreach (KoOdfManifestEntry *entry, manifest) { debugFlake << entry->mediaType() << entry->fullPath() << entry->version(); } #endif // 1. Get the XML contents of the objects from the draw:frame. As // a side effect, this extracts the object names from all // xlink:href and stores them into d->objectNames. The saved // xml contents itself is saved into d->objectXmlContents // (QByteArray) so we can save it back from saveOdf(). d->storeObjects(frameElement); #if 1 // Debug only debugFlake << "----------------------------------------------------------------"; debugFlake << "After storeObjects():"; foreach (ObjectEntry *object, d->objectEntries) { debugFlake << "objectXmlContents: " << object->objectXmlContents << "objectName: " << object->objectName; // Note: at this point, isDir and manifestEntry are not set. #endif } // 2. Loop through the objects that were found in the frame and // save all the files associated with them. Some of the // objects are files, and some are directories. The // directories are searched and the files within are saved as // well. // // In this loop, isDir and manifestEntry of each ObjectEntry are set. bool foundPreview = false; foreach (ObjectEntry *object, d->objectEntries) { QString objectName = object->objectName; if (objectName.isEmpty()) continue; debugFlake << "Storing files for object named:" << objectName; // Try to find out if the entry is a directory. // If the object is a directory, then save all the files // inside it, otherwise save the file as it is. QString dirName = objectName + '/'; bool isDir = !context.odfLoadingContext().mimeTypeForPath(dirName).isEmpty(); if (isDir) { // A directory: the files can be found in the manifest. foreach (KoOdfManifestEntry *entry, manifest) { if (entry->fullPath() == dirName) continue; if (entry->fullPath().startsWith(dirName)) { d->storeFile(entry->fullPath(), context); } } } else { // A file: save it. d->storeFile(objectName, context); } // Get the manifest entry for this object. KoOdfManifestEntry *entry = 0; QString entryName = isDir ? dirName : objectName; for (int j = 0; j < manifest.size(); ++j) { KoOdfManifestEntry *temp = manifest.value(j); if (temp->fullPath() == entryName) { entry = new KoOdfManifestEntry(*temp); break; } } object->isDir = isDir; object->manifestEntry = entry; // If we have not already found a preview in previous times // through the loop, then see if this one may be a preview. if (!foundPreview) { debugFlake << "Attempting to load preview from " << objectName; QByteArray previewData = d->loadFile(objectName, context); // Check to see if we know the mimetype for this entry. Specifically: // 1. Check to see if the item is a loadable SVG file // FIXME: Perhaps check in the manifest first? But this // seems to work well. d->scalablePreview->load(previewData); if (d->scalablePreview->isValid()) { debugFlake << "Found scalable preview image!"; d->scalablePreview->setViewBox(d->scalablePreview->boundsOnElement("svg")); foundPreview = true; continue; } // 2. Otherwise check to see if it's a loadable pixmap file d->pixmapPreview.loadFromData(previewData); if (!d->pixmapPreview.isNull()) { debugFlake << "Found pixel based preview image!"; foundPreview = true; } } } #if 0 // Enable to get more detailed debug messages debugFlake << "Object manifest entries:"; for (int i = 0; i < d->manifestEntries.size(); ++i) { KoOdfManifestEntry *entry = d->manifestEntries.value(i); debugFlake << i << ":" << entry; if (entry) debugFlake << entry->fullPath() << entry->mediaType() << entry->version(); else debugFlake << "--"; } debugFlake << "END LOADING ####################################################"; #endif return true; } // Load the actual contents inside the frame. bool KoUnavailShape::loadOdfFrameElement(const KoXmlElement & /*element*/, KoShapeLoadingContext &/*context*/) { return true; } // ---------------------------------------------------------------- // Private functions void KoUnavailShape::Private::storeObjects(const KoXmlElement &element) { // Loop through all the child elements of the draw:frame and save them. KoXmlNode n = element.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { debugFlake << "In draw:frame, node =" << n.nodeName(); // This disregards #text, but that's not in the spec anyway so // it doesn't need to be saved. if (!n.isElement()) continue; KoXmlElement el = n.toElement(); ObjectEntry *object = new ObjectEntry; QByteArray contentsTmp; QBuffer buffer(&contentsTmp); // the member KoXmlWriter writer(&buffer); // 1. Find out the objectName // Save the normalized filename, i.e. without a starting "./". // An empty string is saved if no name is found. QString name = el.attributeNS(KoXmlNS::xlink, "href", QString()); if (name.startsWith(QLatin1String("./"))) name.remove(0, 2); object->objectName = name; // 2. Copy the XML code. QHash unknownNamespaces; storeXmlRecursive(el, writer, object, unknownNamespaces); object->objectXmlContents = contentsTmp; // 3, 4: the isDir and manifestEntry members are not set here, // but initialize them anyway. . object->isDir = false; // Has to be initialized to something. object->manifestEntry = 0; objectEntries.append(object); } } void KoUnavailShape::Private::storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces) { // Start the element; // keep the name in a QByteArray so that it stays valid until end element is called. const QByteArray name(el.nodeName().toLatin1()); writer.startElement(name.constData()); - // Copy all the attributes, including namespaces. - QList< QPair > attributeNames = el.attributeFullNames(); - for (int i = 0; i < attributeNames.size(); ++i) { - QPair attrPair(attributeNames.value(i)); - if (attrPair.first.isEmpty()) { - writer.addAttribute(attrPair.second.toLatin1(), el.attribute(attrPair.second)); - } - else { - // This somewhat convoluted code is because we need the - // namespace, not the namespace URI. - QString nsShort = KoXmlNS::nsURI2NS(attrPair.first.toLatin1()); - // in case we don't find the namespace in our list create a own one and use that - // so the document created on saving is valid. - if (nsShort.isEmpty()) { - nsShort = unknownNamespaces.value(attrPair.first); - if (nsShort.isEmpty()) { - nsShort = QString("ns%1").arg(unknownNamespaces.size() + 1); - unknownNamespaces.insert(attrPair.first, nsShort); - } - QString name = QString("xmlns:") + nsShort; - writer.addAttribute(name.toLatin1(), attrPair.first.toLatin1()); - } - QString attr(nsShort + ':' + attrPair.second); - writer.addAttribute(attr.toLatin1(), el.attributeNS(attrPair.first, - attrPair.second)); - } - } - // Child elements // Loop through all the child elements of the draw:frame. KoXmlNode n = el.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { storeXmlRecursive(n.toElement(), writer, object, unknownNamespaces); } else if (n.isText()) { writer.addTextNode(n.toText().data()/*.toUtf8()*/); } } // End the element writer.endElement(); } /** * This function stores the embedded file in an internal store - it does not save files to disk, * and thus it is named in this manner, to avoid the function being confused with functions which * save files to disk. */ void KoUnavailShape::Private::storeFile(const QString &fileName, KoShapeLoadingContext &context) { debugFlake << "Saving file: " << fileName; // Directories need to be saved too, but they don't have any file contents. if (fileName.endsWith('/')) { FileEntry *entry = new FileEntry; entry->path = fileName; entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = true; embeddedFiles.append(entry); } QByteArray fileContent = loadFile(fileName, context); if (fileContent.isNull()) return; // Actually store the file in the list. FileEntry *entry = new FileEntry; entry->path = fileName; if (entry->path.startsWith(QLatin1String("./"))) entry->path.remove(0, 2); entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = false; entry->contents = fileContent; embeddedFiles.append(entry); debugFlake << "File length: " << fileContent.size(); } QByteArray KoUnavailShape::Private::loadFile(const QString &fileName, KoShapeLoadingContext &context) { // Can't load a file which is a directory, return an invalid QByteArray if (fileName.endsWith('/')) return QByteArray(); KoStore *store = context.odfLoadingContext().store(); QByteArray fileContent; if (!store->open(fileName)) { store->close(); return QByteArray(); } int fileSize = store->size(); fileContent = store->read(fileSize); store->close(); //debugFlake << "File content: " << fileContent; return fileContent; } diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp new file mode 100644 index 0000000000..9b8e88433a --- /dev/null +++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp @@ -0,0 +1,253 @@ +/* This file is part of the KDE project + + Copyright (c) 2017 L. E. Segovia + + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include "kis_debug.h" + +#include +#include +#include +#include +#include +#include + +struct KoSvgSymbolCollectionResource::Private { + QVector symbols; + QString title; + QString description; +}; + + +KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const QString& filename) + : KoResource(filename) + , d(new Private()) +{ +} + +KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource() + : KoResource(QString()) + , d(new Private()) +{ +} + +KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs) + : QObject(0) + , KoResource(QString()) + , d(new Private()) +{ + setFilename(rhs.filename()); + d->symbols = rhs.d->symbols; + setValid(true); +} + +KoSvgSymbolCollectionResource::~KoSvgSymbolCollectionResource() +{ +} + +bool KoSvgSymbolCollectionResource::load() +{ + QFile file(filename()); + if (file.size() == 0) return false; + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + bool res = loadFromDevice(&file); + file.close(); + return res; +} + + +void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) +{ + QList shapes = group->shapes(); + qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + Q_FOREACH (KoShape *child, shapes) { + // we paint recursively here, so we do not have to check recursively for visibility + if (!child->isVisible()) + continue; + KoShapeGroup *childGroup = dynamic_cast(child); + if (childGroup) { + paintGroup(childGroup, painter, converter, paintContext); + } else { + painter.save(); + KoShapeManager::renderSingleShape(child, painter, converter, paintContext); + painter.restore(); + } + } + +} + +bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev) +{ + if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); + + KoXmlDocument doc; + QString errorMsg; + int errorLine = 0; + int errorColumn; + + bool ok = doc.setContent(dev->readAll(), false, &errorMsg, &errorLine, &errorColumn); + if (!ok) { + + errKrita << "Parsing error in " << filename() << "! Aborting!" << endl + << " In line: " << errorLine << ", column: " << errorColumn << endl + << " Error message: " << errorMsg << endl; + errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" + , errorLine , errorColumn , errorMsg); + return false; + } + + KoDocumentResourceManager manager; + SvgParser parser(&manager); + parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values + QSizeF fragmentSize; + // We're not interested in the shapes themselves + qDeleteAll(parser.parseSvg(doc.documentElement(), &fragmentSize)); + d->symbols = parser.takeSymbols(); +// qDebug() << "Loaded" << filename() << "\n\t" +// << "Title" << parser.documentTitle() << "\n\t" +// << "Description" << parser.documentDescription() +// << "\n\tgot" << d->symbols.size() << "symbols" +// << d->symbols[0]->shape->outlineRect() +// << d->symbols[0]->shape->size(); + + d->title = parser.documentTitle(); + setName(d->title); + d->description = parser.documentDescription(); + + if (d->symbols.size() < 1) { + setValid(false); + return false; + } + setValid(true);; + + for(int i = 0; i < d->symbols.size(); ++i) { + + KoSvgSymbol *symbol = d->symbols[i]; + KoShapeGroup *group = dynamic_cast(symbol->shape); + Q_ASSERT(group); + + QRectF rc = group->boundingRect().normalized(); + + QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied); + QPainter gc(&image); + image.fill(Qt::gray); + + KoViewConverter vc; + KoShapePaintingContext ctx; + +// qDebug() << "Going to render. Original bounding rect:" << group->boundingRect() +// << "Normalized: " << rc +// << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height(); + + gc.translate(-rc.x(), -rc.y()); + paintGroup(group, gc, vc, ctx); + gc.end(); + d->symbols[i]->icon = image.scaled(500, 500, Qt::KeepAspectRatio); + + } + + setImage(d->symbols[0]->icon); + return true; +} + +bool KoSvgSymbolCollectionResource::save() +{ + QFile file(filename()); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + saveToDevice(&file); + file.close(); + return true; +} + +bool KoSvgSymbolCollectionResource::saveToDevice(QIODevice *dev) const +{ + bool res = false; + // XXX + if (res) { + KoResource::saveToDevice(dev); + } + return res; +} + +QString KoSvgSymbolCollectionResource::defaultFileExtension() const +{ + return QString(".svg"); +} + +QString KoSvgSymbolCollectionResource::title() const +{ + return d->title; +} + +QString KoSvgSymbolCollectionResource::description() const +{ + return d->description; +} + +QString KoSvgSymbolCollectionResource::creator() const +{ + return ""; +} + +QString KoSvgSymbolCollectionResource::rights() const +{ + return ""; +} + +QString KoSvgSymbolCollectionResource::language() const +{ + return ""; +} + +QStringList KoSvgSymbolCollectionResource::subjects() const +{ + return QStringList(); +} + +QString KoSvgSymbolCollectionResource::license() const +{ + return ""; +} + +QStringList KoSvgSymbolCollectionResource::permits() const +{ + return QStringList(); +} + +QVector KoSvgSymbolCollectionResource::symbols() const +{ + return d->symbols; +} diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.h b/libs/flake/resources/KoSvgSymbolCollectionResource.h new file mode 100644 index 0000000000..58fcc3de6e --- /dev/null +++ b/libs/flake/resources/KoSvgSymbolCollectionResource.h @@ -0,0 +1,101 @@ +/* This file is part of the KDE project + Copyright (c) 2017 Boudewijn Rempt + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + */ +#ifndef KOSVGSYMBOLCOLLECTIONRESOURCE +#define KOSVGSYMBOLCOLLECTIONRESOURCE + +#include +#include +#include +#include +#include + +#include + +#include + + +#include "kritaflake_export.h" + +struct KoSvgSymbol { + KoSvgSymbol() {} + KoSvgSymbol(const QString &_title) + : title(_title) {} + + ~KoSvgSymbol() + { + delete shape; + } + + QString id; + QString title; + KoShape *shape; + QImage icon; + + bool operator==(const KoSvgSymbol& rhs) const { + return title == rhs.title; + } +}; + +/** + * Loads an svg file that contains "symbol" objects and creates a collection of those objects. + */ +class KRITAFLAKE_EXPORT KoSvgSymbolCollectionResource : public QObject, public KoResource +{ + Q_OBJECT +public: + + /** + */ + explicit KoSvgSymbolCollectionResource(const QString &filename); + + /// Create an empty color set + KoSvgSymbolCollectionResource(); + + /// Explicit copy constructor (KoResource copy constructor is private) + KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs); + + ~KoSvgSymbolCollectionResource() override; + + bool load() override; + bool loadFromDevice(QIODevice *dev) override; + bool save() override; + bool saveToDevice(QIODevice* dev) const override; + + QString defaultFileExtension() const override; + + QString title() const; + QString description() const; + QString creator() const; + QString rights() const; + QString language() const; + QStringList subjects() const; + QString license() const; + QStringList permits() const; + + QVector symbols() const; + + +private: + + struct Private; + const QScopedPointer d; + +}; +#endif // KoSvgSymbolCollectionResource + diff --git a/libs/flake/svg/SvgCssHelper.h b/libs/flake/svg/SvgCssHelper.h index a3f26dfc32..5ddd5b3141 100644 --- a/libs/flake/svg/SvgCssHelper.h +++ b/libs/flake/svg/SvgCssHelper.h @@ -1,48 +1,48 @@ /* 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 SVGCSSHELPER_H #define SVGCSSHELPER_H #include -class KoXmlElement; +#include class SvgCssHelper { public: SvgCssHelper(); ~SvgCssHelper(); /// Parses css style sheet in given xml element void parseStylesheet(const KoXmlElement &); /** * Matches css styles to given xml element and returns them * @param element the element to match styles for * @return list of matching css styles sorted by priority */ QStringList matchStyles(const KoXmlElement &element) const; private: class Private; Private * const d; }; #endif // SVGCSSHELPER_H diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp index 970730f12f..4b38d9daa0 100644 --- a/libs/flake/svg/SvgParser.cpp +++ b/libs/flake/svg/SvgParser.cpp @@ -1,1544 +1,1622 @@ /* This file is part of the KDE project * Copyright (C) 2002-2005,2007 Rob Buis * Copyright (C) 2002-2004 Nicolas Goutte * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2009 Jan Hambrecht * Copyright (C) 2005,2007 Thomas Zander * Copyright (C) 2006-2007 Inge Wallin * Copyright (C) 2007-2008,2010 Thorsten Zachmann * 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 "SvgParser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include "KoFilterEffectStack.h" #include "KoFilterEffectLoadingContext.h" #include #include #include #include "SvgUtil.h" #include "SvgShape.h" #include "SvgGraphicContext.h" #include "SvgFilterHelper.h" #include "SvgGradientHelper.h" #include "SvgClipPathHelper.h" #include "parsers/SvgTransformParser.h" #include "kis_pointer_utils.h" #include #include #include "kis_debug.h" #include "kis_global.h" SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager) : m_context(documentResourceManager) , m_documentResourceManager(documentResourceManager) { } SvgParser::~SvgParser() { + qDeleteAll(m_symbols); } void SvgParser::setXmlBaseDir(const QString &baseDir) { m_context.setInitialXmlBaseDir(baseDir); setFileFetcher( [this](const QString &name) { const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name; QFile file(fileName); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(file.exists(), QByteArray()); file.open(QIODevice::ReadOnly); return file.readAll(); }); } void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch) { KIS_ASSERT(!m_context.currentGC()); m_context.pushGraphicsContext(); m_context.currentGC()->pixelsPerInch = pixelsPerInch; const qreal scale = 72.0 / pixelsPerInch; const QTransform t = QTransform::fromScale(scale, scale); m_context.currentGC()->currentBoundingBox = boundsInPixels; m_context.currentGC()->matrix = t; } QList SvgParser::shapes() const { return m_shapes; } +QVector SvgParser::takeSymbols() +{ + QVector symbols = m_symbols; + m_symbols.clear(); + return symbols; +} + // Helper functions // --------------------------------------------------------------------------------------- SvgGradientHelper* SvgParser::findGradient(const QString &id) { SvgGradientHelper *result = 0; // check if gradient was already parsed, and return it if (m_gradients.contains(id)) { result = &m_gradients[ id ]; } // check if gradient was stored for later parsing if (!result && m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName().contains("Gradient")) { result = parseGradient(m_context.definition(id)); } } return result; } QSharedPointer SvgParser::findPattern(const QString &id, const KoShape *shape) { QSharedPointer result; // check if gradient was stored for later parsing if (m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName() == "pattern") { result = parsePattern(m_context.definition(id), shape); } } return result; } SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href) { // check if filter was already parsed, and return it if (m_filters.contains(id)) return &m_filters[ id ]; // check if filter was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); - if (e.childNodesCount() == 0) { + if (KoXml::childNodesCount(e) == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findFilter(mhref, id); else return 0; } else { // ok parse filter now if (! parseFilter(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed filter or 0 QString n; if (href.isEmpty()) n = id; else n = href; if (m_filters.contains(n)) return &m_filters[ n ]; else return 0; } SvgClipPathHelper* SvgParser::findClipPath(const QString &id) { return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0; } // Parsing functions // --------------------------------------------------------------------------------------- qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox) { return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox); } qreal SvgParser::parseUnitX(const QString &unit) { return SvgUtil::parseUnitX(m_context.currentGC(), unit); } qreal SvgParser::parseUnitY(const QString &unit) { return SvgUtil::parseUnitY(m_context.currentGC(), unit); } qreal SvgParser::parseUnitXY(const QString &unit) { return SvgUtil::parseUnitXY(m_context.currentGC(), unit); } qreal SvgParser::parseAngular(const QString &unit) { return SvgUtil::parseUnitAngular(m_context.currentGC(), unit); } SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return 0; SvgGradientHelper gradHelper; QString gradientId = e.attribute("id"); if (gradientId.isEmpty()) return 0; // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.contains(gradientId)) { return &m_gradients[gradientId]; } if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) { gradHelper = *pGrad; } } } const QGradientStops defaultStops = gradHelper.gradient()->stops(); if (e.attribute("gradientUnits") == "userSpaceOnUse") { gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse); } m_context.pushGraphicsContext(e); uploadStyleToContext(e); if (e.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")), SvgUtil::fromPercentage(e.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")), SvgUtil::fromPercentage(e.attribute("y2", "0%")))); } else { g->setStart(QPointF(parseUnitX(e.attribute("x1")), parseUnitY(e.attribute("y1")))); g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")), parseUnitY(e.attribute("y2")))); } gradHelper.setGradient(g); } else if (e.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")), SvgUtil::fromPercentage(e.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")), SvgUtil::fromPercentage(e.attribute("fy", "50%")))); } else { g->setCenter(QPointF(parseUnitX(e.attribute("cx")), parseUnitY(e.attribute("cy")))); g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")), parseUnitY(e.attribute("fy")))); g->setRadius(parseUnitXY(e.attribute("r"))); } gradHelper.setGradient(g); } else { qDebug() << "WARNING: Failed to parse gradient with tag" << e.tagName(); } // handle spread method QGradient::Spread spreadMethod = QGradient::PadSpread; QString spreadMethodStr = e.attribute("spreadMethod"); if (!spreadMethodStr.isEmpty()) { if (spreadMethodStr == "reflect") { spreadMethod = QGradient::ReflectSpread; } else if (spreadMethodStr == "repeat") { spreadMethod = QGradient::RepeatSpread; } } gradHelper.setSpreadMode(spreadMethod); // Parse the color stops. m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops); if (e.hasAttribute("gradientTransform")) { SvgTransformParser p(e.attribute("gradientTransform")); if (p.isValid()) { gradHelper.setTransform(p.transform()); } } m_context.popGraphicsContext(); m_gradients.insert(gradientId, gradHelper); return &m_gradients[gradientId]; } inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset) { QTransform result = patternTransform * QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) * patternTransform.inverted(); KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate); return QPointF(result.dx(), result.dy()); } QSharedPointer SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape) { /** * Unlike the gradient parsing function, this method is called every time we * *reference* the pattern, not when we define it. Therefore we can already * use the coordinate system of the destination. */ QSharedPointer pattHelper; SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return pattHelper; const QString patternId = e.attribute("id"); if (patternId.isEmpty()) return pattHelper; pattHelper = toQShared(new KoVectorPatternBackground); if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty() &&href != patternId) { // copy the referenced pattern if found QSharedPointer pPatt = findPattern(href, shape); if (pPatt) { pattHelper = pPatt; } } } pattHelper->setReferenceCoordinates( KoFlake::coordinatesFromString(e.attribute("patternUnits"), pattHelper->referenceCoordinates())); pattHelper->setContentCoordinates( KoFlake::coordinatesFromString(e.attribute("patternContentUnits"), pattHelper->contentCoordinates())); if (e.hasAttribute("patternTransform")) { SvgTransformParser p(e.attribute("patternTransform")); if (p.isValid()) { pattHelper->setPatternTransform(p.transform()); } } if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) { QRectF referenceRect( SvgUtil::fromPercentage(e.attribute("x", "0%")), SvgUtil::fromPercentage(e.attribute("y", "0%")), SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why! SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } else { QRectF referenceRect( parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")), parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why! parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } /** * In Krita shapes X,Y coordinates are baked into the the shape global transform, but * the pattern should be painted in "user" coordinates. Therefore, we should handle * this offfset separately. * - * TODO: Please also not that this offset is different from extraShapeOffset(), + * TODO: Please also note that this offset is different from extraShapeOffset(), * because A.inverted() * B != A * B.inverted(). I'm not sure which variant is * correct (DK) */ const QTransform dstShapeTransform = shape->absoluteTransformation(0); const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate); const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy()); m_context.pushGraphicsContext(e); gc = m_context.currentGC(); gc->workaroundClearInheritedFillProperties(); // HACK! // start building shape tree from scratch gc->matrix = QTransform(); const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/; const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes! // although we expect the pattern be reusable, but it is not so! // WARNING2: the pattern shapes are stored in *User* coordinate system, although // the "official" content system might be either OBB or User. It means that // this baked transform should be stripped before writing the shapes back // into SVG if (e.hasAttribute("viewBox")) { gc->currentBoundingBox = pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ? relativeToShape.mapRect(pattHelper->referenceRect()) : pattHelper->referenceRect(); applyViewBoxTransform(e); pattHelper->setContentCoordinates(pattHelper->referenceCoordinates()); } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) { gc->matrix = relativeToShape * gc->matrix; } // We do *not* apply patternTransform here! Here we only bake the untransformed // version of the shape. The transformed one will be done in the very end while rendering. QList patternShapes = parseContainer(e); if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into the pattern shapes const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); Q_FOREACH (KoShape *shape, patternShapes) { shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y())); } } if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into reference rect // NOTE: this is possible *only* when pattern transform is not perspective // (which is always true for SVG) const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); QRectF ref = pattHelper->referenceRect(); ref.translate(offset); pattHelper->setReferenceRect(ref); } m_context.popGraphicsContext(); gc = m_context.currentGC(); if (!patternShapes.isEmpty()) { pattHelper->setShapes(patternShapes); } return pattHelper; } bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgFilterHelper filter; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another filter if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced filter if found SvgFilterHelper *refFilter = findFilter(href); if (refFilter) filter = *refFilter; } } else { filter.setContent(b); } if (b.attribute("filterUnits") == "userSpaceOnUse") filter.setFilterUnits(KoFlake::UserSpaceOnUse); if (b.attribute("primitiveUnits") == "objectBoundingBox") filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox); // parse filter region rectangle if (filter.filterUnits() == KoFlake::UserSpaceOnUse) { filter.setPosition(QPointF(parseUnitX(b.attribute("x")), parseUnitY(b.attribute("y")))); filter.setSize(QSizeF(parseUnitX(b.attribute("width")), parseUnitY(b.attribute("height")))); } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")), SvgUtil::fromPercentage(b.attribute("y", "-0.1")))); filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")), SvgUtil::fromPercentage(b.attribute("height", "1.2")))); } m_filters.insert(b.attribute("id"), filter); return true; } bool SvgParser::parseMarker(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; QScopedPointer marker(new KoMarker()); marker->setCoordinateSystem( KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth"))); marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")), parseUnitY(e.attribute("refY")))); marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")), parseUnitY(e.attribute("markerHeight", "3")))); const QString orientation = e.attribute("orient", "0"); if (orientation == "auto") { marker->setAutoOrientation(true); } else { marker->setExplicitOrientation(parseAngular(orientation)); } // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize()); KoShape *markerShape = parseGroup(e); m_context.popGraphicsContext(); if (!markerShape) return false; marker->setShapes({markerShape}); m_markers.insert(id, QExplicitlySharedDataPointer(marker.take())); return true; } +bool SvgParser::parseSymbol(const KoXmlElement &e) +{ + const QString id = e.attribute("id"); + + if (id.isEmpty()) return false; + + KoSvgSymbol *svgSymbol = new KoSvgSymbol(); + + // ensure that the clip path is loaded in local coordinates system + m_context.pushGraphicsContext(e, false); + m_context.currentGC()->matrix = QTransform(); + m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0); + + QString title = e.firstChildElement("title").toElement().text(); + + KoShape *symbolShape = parseGroup(e); + + m_context.popGraphicsContext(); + + if (!symbolShape) return false; + + svgSymbol->shape = symbolShape; + svgSymbol->title = title; + svgSymbol->id = id; + if (title.isEmpty()) svgSymbol->title = id; + + if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) { + warnFlake << "Symbol" << id << "seems to be empty, discarding"; + delete svgSymbol; + return false; + } + + m_symbols << svgSymbol; + + return true; +} + bool SvgParser::parseClipPath(const KoXmlElement &e) { SvgClipPathHelper clipPath; const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipPath.setClipPathUnits( KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse)); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipPath.setShapes({clipShape}); m_clipPaths.insert(id, clipPath); return true; } bool SvgParser::parseClipMask(const KoXmlElement &e) { QSharedPointer clipMask(new KoClipMask); const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox)); clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse)); QRectF maskRect; if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) { maskRect.setRect( SvgUtil::fromPercentage(e.attribute("x", "-10%")), SvgUtil::fromPercentage(e.attribute("y", "-10%")), SvgUtil::fromPercentage(e.attribute("width", "120%")), SvgUtil::fromPercentage(e.attribute("height", "120%"))); } else { maskRect.setRect( parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case, parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us... parseUnitX(e.attribute("width", "120%")), parseUnitY(e.attribute("height", "120%"))); } clipMask->setMaskRect(maskRect); // ensure that the clip mask is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipMask->setShapes({clipShape}); m_clipMasks.insert(id, clipMask); return true; } void SvgParser::uploadStyleToContext(const KoXmlElement &e) { SvgStyles styles = m_context.styleParser().collectStyles(e); m_context.styleParser().parseFont(styles); m_context.styleParser().parseStyle(styles); } void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { if (!shape) return; SvgGraphicsContext *gc = m_context.currentGC(); KIS_ASSERT(gc); if (!dynamic_cast(shape)) { applyFillStyle(shape); applyStrokeStyle(shape); } if (KoPathShape *pathShape = dynamic_cast(shape)) { applyMarkers(pathShape); } applyFilter(shape); applyClipping(shape, shapeToOriginalUserCoordinates); applyMaskClipping(shape, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { /** * WARNING: here is a small inconsistency with the standard: * in the standard, 'display' is not inherited, but in * flake it is! * * NOTE: though the standard says: "A value of 'display:none' indicates * that the given element and ***its children*** shall not be * rendered directly". Therefore, using setVisible(false) is fully * legitimate here (DK 29.11.16). */ shape->setVisible(false); } shape->setTransparency(1.0 - gc->opacity); } void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates) { applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates); } void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; m_context.styleParser().parseStyle(styles); if (!obj) return; if (!dynamic_cast(obj)) { applyFillStyle(obj); applyStrokeStyle(obj); } if (KoPathShape *pathShape = dynamic_cast(obj)) { applyMarkers(pathShape); } applyFilter(obj); applyClipping(obj, shapeToOriginalUserCoordinates); applyMaskClipping(obj, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { obj->setVisible(false); } obj->setTransparency(1.0 - gc->opacity); } QGradient* prepareGradientForShape(const SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc, QTransform *transform) { QGradient *resultGradient = 0; KIS_ASSERT(transform); if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) { resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform(); } else { if (gradient->gradient()->type() == QGradient::LinearGradient) { /** * Create a converted gradient that looks the same, but linked to the * bounding rect of the shape, so it would be transformed with the shape */ const QRectF boundingRect = shape->outline().boundingRect(); const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); const QTransform relativeToUser = relativeToShape * shape->transformation() * gc->matrix.inverted(); const QTransform userToRelative = relativeToUser.inverted(); const QLinearGradient *o = static_cast(gradient->gradient()); QLinearGradient *g = new QLinearGradient(); g->setStart(userToRelative.map(o->start())); g->setFinalStop(userToRelative.map(o->finalStop())); g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStops(o->stops()); g->setSpread(o->spread()); resultGradient = g; *transform = relativeToUser * gradient->transform() * userToRelative; } else if (gradient->gradient()->type() == QGradient::RadialGradient) { // For radial and conical gradients such conversion is not possible resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform() * gc->matrix * shape->transformation().inverted(); } } return resultGradient; } void SvgParser::applyFillStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->fillType == SvgGraphicsContext::None) { shape->setBackground(QSharedPointer(0)); } else if (gc->fillType == SvgGraphicsContext::Solid) { shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } else if (gc->fillType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->fillId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QSharedPointer bg; bg = toQShared(new KoGradientBackground(result)); bg->setTransform(transform); shape->setBackground(bg); } } else { QSharedPointer pattern = findPattern(gc->fillId, shape); if (pattern) { shape->setBackground(pattern); } else { // no referenced fill found, use fallback color shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } } } KoPathShape *path = dynamic_cast(shape); if (path) path->setFillRule(gc->fillRule); } void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke) { const double lineWidth = srcStroke->lineWidth(); QVector dashes = srcStroke->lineDashes(); // apply line width to dashes and dash offset if (dashes.count() && lineWidth > 0.0) { const double dashOffset = srcStroke->dashOffset(); QVector dashes = srcStroke->lineDashes(); for (int i = 0; i < dashes.count(); ++i) { dashes[i] /= lineWidth; } dstStroke->setLineStyle(Qt::CustomDashLine, dashes); dstStroke->setDashOffset(dashOffset / lineWidth); } else { dstStroke->setLineStyle(Qt::SolidLine, QVector()); } } void SvgParser::applyStrokeStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->strokeType == SvgGraphicsContext::None) { shape->setStroke(KoShapeStrokeModelSP()); } else if (gc->strokeType == SvgGraphicsContext::Solid) { KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } else if (gc->strokeType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->strokeId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QBrush brush = *result; delete result; brush.setTransform(transform); KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); stroke->setLineBrush(brush); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } else { // no referenced stroke found, use fallback color KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } } void SvgParser::applyFilter(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->filterId.isEmpty()) return; SvgFilterHelper *filter = findFilter(gc->filterId); if (! filter) return; KoXmlElement content = filter->content(); // parse filter region QRectF bound(shape->position(), shape->size()); // work on bounding box without viewbox tranformation applied // so user space coordinates of bounding box and filter region match up bound = gc->viewboxTransform.inverted().mapRect(bound); QRectF filterRegion(filter->position(bound), filter->size(bound)); // convert filter region to boundingbox units QRectF objectFilterRegion; objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound)); objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound)); KoFilterEffectLoadingContext context(m_context.xmlBaseDir()); context.setShapeBoundingBox(bound); // enable units conversion context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse); context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse); KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance(); KoFilterEffectStack *filterStack = 0; QSet stdInputs; stdInputs << "SourceGraphic" << "SourceAlpha"; stdInputs << "BackgroundImage" << "BackgroundAlpha"; stdInputs << "FillPaint" << "StrokePaint"; QMap inputs; // create the filter effects and add them to the shape for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement primitive = n.toElement(); KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context); if (!filterEffect) { debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet"; continue; } const QString input = primitive.attribute("in"); if (!input.isEmpty()) { filterEffect->setInput(0, input); } const QString output = primitive.attribute("result"); if (!output.isEmpty()) { filterEffect->setOutput(output); } QRectF subRegion; // parse subregion if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) { const QString xa = primitive.attribute("x"); const QString ya = primitive.attribute("y"); const QString wa = primitive.attribute("width"); const QString ha = primitive.attribute("height"); if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) { bool hasStdInput = false; bool isFirstEffect = filterStack == 0; // check if one of the inputs is a standard input Q_FOREACH (const QString &input, filterEffect->inputs()) { if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) { hasStdInput = true; break; } } if (hasStdInput || primitive.tagName() == "feImage") { // default to 0%, 0%, 100%, 100% subRegion.setTopLeft(QPointF(0, 0)); subRegion.setSize(QSizeF(1, 1)); } else { // defaults to bounding rect of all referenced nodes Q_FOREACH (const QString &input, filterEffect->inputs()) { if (!inputs.contains(input)) continue; KoFilterEffect *inputFilter = inputs[input]; if (inputFilter) subRegion |= inputFilter->filterRect(); } } } else { const qreal x = parseUnitX(xa); const qreal y = parseUnitY(ya); const qreal w = parseUnitX(wa); const qreal h = parseUnitY(ha); subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound)); subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound)); } } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0")); const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0")); const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1")); const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1")); subRegion = QRectF(QPointF(x, y), QSizeF(w, h)); } filterEffect->setFilterRect(subRegion); if (!filterStack) filterStack = new KoFilterEffectStack(); filterStack->appendFilterEffect(filterEffect); inputs[filterEffect->output()] = filterEffect; } if (filterStack) { filterStack->setClipRect(objectFilterRegion); shape->setFilterEffectStack(filterStack); } } void SvgParser::applyMarkers(KoPathShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) { shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker); } if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) { shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker); } if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) { shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker); } shape->setAutoFillMarkers(gc->autoFillMarkers); } void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->clipPathId.isEmpty()) return; SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId); if (!clipPath || clipPath->isEmpty()) return; QList shapes; Q_FOREACH (KoShape *item, clipPath->shapes()) { KoShape *clonedShape = item->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } if (!shapeToOriginalUserCoordinates.isNull()) { const QTransform t = QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(), shapeToOriginalUserCoordinates.y()); Q_FOREACH(KoShape *s, shapes) { s->applyAbsoluteTransformation(t); } } KoClipPath *clipPathObject = new KoClipPath(shapes, clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ? KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse); shape->setClipPath(clipPathObject); } void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (gc->clipMaskId.isEmpty()) return; QSharedPointer originalClipMask = m_clipMasks.value(gc->clipMaskId); if (!originalClipMask || originalClipMask->isEmpty()) return; KoClipMask *clipMask = originalClipMask->clone(); clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates); shape->setClipMask(clipMask); } KoShape* SvgParser::parseUse(const KoXmlElement &e) { KoShape *result = 0; QString id = e.attribute("xlink:href"); if (!id.isEmpty()) { QString key = id.mid(1); if (m_context.hasDefinition(key)) { SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); // TODO: parse 'width' and 'hight' as well gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0"))); const KoXmlElement &referencedElement = m_context.definition(key); result = parseGroup(e, referencedElement); m_context.popGraphicsContext(); } } return result; } void SvgParser::addToGroup(QList shapes, KoShapeGroup *group) { m_shapes += shapes; if (!group || shapes.isEmpty()) return; // not clipped, but inherit transform KoShapeGroupCommand cmd(group, shapes, false, true, false); cmd.redo(); } QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize) { // check if we are the root svg element const bool isRootSvg = m_context.isRootContext(); // parse 'transform' field if preset SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); applyStyle(0, e, QPointF()); const QString w = e.attribute("width"); const QString h = e.attribute("height"); const qreal width = w.isEmpty() ? 666.0 : parseUnitX(w); const qreal height = h.isEmpty() ? 555.0 : parseUnitY(h); QSizeF svgFragmentSize(QSizeF(width, height)); if (fragmentSize) { *fragmentSize = svgFragmentSize; } gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize); if (!isRootSvg) { // x and y attribute has no meaning for outermost svg elements const qreal x = parseUnit(e.attribute("x", "0")); const qreal y = parseUnit(e.attribute("y", "0")); QTransform move = QTransform::fromTranslate(x, y); gc->matrix = move * gc->matrix; } applyViewBoxTransform(e); QList shapes; - // SVG 1.1: skip the rendering of the element if it has null viewBox - if (gc->currentBoundingBox.isValid()) { + // First find the metadata + for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + KoXmlElement b = n.toElement(); + if (b.isNull()) + continue; + + if (b.tagName() == "title") { + m_documentTitle = b.text().trimmed(); + } + else if (b.tagName() == "desc") { + m_documentDescription = b.text().trimmed(); + } + else if (b.tagName() == "metadata") { + // TODO: parse the metadata + } + } + + + // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy + // and as mother makes them -- if mother is inkscape. + if (gc->currentBoundingBox.normalized().isValid()) { shapes = parseContainer(e); } m_context.popGraphicsContext(); return shapes; } void SvgParser::applyViewBoxTransform(const KoXmlElement &element) { SvgGraphicsContext *gc = m_context.currentGC(); QRectF viewRect = gc->currentBoundingBox; QTransform viewTransform; if (SvgUtil::parseViewBox(gc, element, gc->currentBoundingBox, &viewRect, &viewTransform)) { gc->matrix = viewTransform * gc->matrix; gc->currentBoundingBox = viewRect; } } QList > SvgParser::knownMarkers() const { return m_markers.values(); } +QString SvgParser::documentTitle() const +{ + return m_documentTitle; +} + +QString SvgParser::documentDescription() const +{ + return m_documentDescription; +} + void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func) { m_context.setFileFetcher(func); } inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading) { const QTransform shapeToOriginalUserCoordinates = shape->absoluteTransformation(0).inverted() * coordinateSystemOnLoading; KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate); return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy()); } KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom) { m_context.pushGraphicsContext(b); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(m_context.nextZIndex()); // groups should also have their own coordinate system! group->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix); uploadStyleToContext(b); QList childShapes; if (!overrideChildrenFrom.isNull()) { // we upload styles from both: and uploadStyleToContext(overrideChildrenFrom); childShapes = parseSingleElement(overrideChildrenFrom); } else { childShapes = parseContainer(b); } // handle id applyId(b.attribute("id"), group); addToGroup(childShapes, group); applyCurrentStyle(group, extraOffset); // apply style to this group after size is set m_context.popGraphicsContext(); return group; } QList SvgParser::parseContainer(const KoXmlElement &e) { QList shapes; // are we parsing a switch container bool isSwitch = e.tagName() == "switch"; for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (isSwitch) { // if we are parsing a switch check the requiredFeatures, requiredExtensions // and systemLanguage attributes // TODO: evaluate feature list if (b.hasAttribute("requiredFeatures")) { continue; } if (b.hasAttribute("requiredExtensions")) { // we do not support any extensions continue; } if (b.hasAttribute("systemLanguage")) { // not implemeted yet } } QList currentShapes = parseSingleElement(b); shapes.append(currentShapes); // if we are parsing a switch, stop after the first supported element if (isSwitch && !currentShapes.isEmpty()) break; } return shapes; } QList SvgParser::parseSingleElement(const KoXmlElement &b) { QList shapes; // save definition for later instantiation with 'use' m_context.addDefinition(b); if (b.tagName() == "svg") { shapes += parseSvg(b); - } else if (b.tagName() == "g" || b.tagName() == "a" || b.tagName() == "symbol") { + } else if (b.tagName() == "g" || b.tagName() == "a") { // treat svg link as group so we don't miss its child elements shapes += parseGroup(b); } else if (b.tagName() == "switch") { m_context.pushGraphicsContext(b); shapes += parseContainer(b); m_context.popGraphicsContext(); } else if (b.tagName() == "defs") { - if (b.childNodesCount() > 0) { + if (KoXml::childNodesCount(b) > 0) { /** * WARNING: 'defs' are basically 'display:none' style, therefore they should not play * any role in shapes outline calculation. But setVisible(false) shapes do! * Should be fixed in the future! */ KoShape *defsShape = parseGroup(b); defsShape->setVisible(false); + delete defsShape; } } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") { } else if (b.tagName() == "pattern") { } else if (b.tagName() == "filter") { parseFilter(b); } else if (b.tagName() == "clipPath") { parseClipPath(b); } else if (b.tagName() == "mask") { parseClipMask(b); } else if (b.tagName() == "marker") { parseMarker(b); + } else if (b.tagName() == "symbol") { + parseSymbol(b); } else if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline" || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image" || b.tagName() == "text") { KoShape *shape = createObjectDirect(b); if (shape) shapes.append(shape); } else if (b.tagName() == "use") { shapes += parseUse(b); } else if (b.tagName() == "color-profile") { m_context.parseProfile(b); } else { // this is an unknown element, so try to load it anyway // there might be a shape that handles that element KoShape *shape = createObject(b); if (shape) { shapes.append(shape); } } return shapes; } // Creating functions // --------------------------------------------------------------------------------------- KoShape * SvgParser::createPath(const KoXmlElement &element) { KoShape *obj = 0; if (element.tagName() == "line") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1")); double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1")); double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2")); double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2")); path->clear(); path->moveTo(QPointF(x1, y1)); path->lineTo(QPointF(x2, y2)); path->normalize(); obj = path; } } else if (element.tagName() == "polyline" || element.tagName() == "polygon") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); bool bFirst = true; QString points = element.attribute("points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); QStringList pointList = points.split(' ', QString::SkipEmptyParts); for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) { QPointF point; point.setX(SvgUtil::fromUserSpace((*it).toDouble())); ++it; if (it == pointList.end()) break; point.setY(SvgUtil::fromUserSpace((*it).toDouble())); if (bFirst) { path->moveTo(point); bFirst = false; } else path->lineTo(point); } if (element.tagName() == "polygon") path->close(); path->setPosition(path->normalize()); obj = path; } } else if (element.tagName() == "path") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(element.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); obj = path; } } return obj; } KoShape * SvgParser::createObjectDirect(const KoXmlElement &b) { m_context.pushGraphicsContext(b); uploadStyleToContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); applyCurrentStyle(obj, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style) { m_context.pushGraphicsContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style; m_context.styleParser().parseFont(objStyle); applyStyle(obj, objStyle, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context) { KoShape *object = 0; const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element); QList factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName); foreach (KoShapeFactoryBase *f, factories) { KoShape *shape = f->createDefaultShape(m_documentResourceManager); if (!shape) continue; SvgShape *svgShape = dynamic_cast(shape); if (!svgShape) { delete shape; continue; } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); if (!svgShape->loadSvg(element, context)) { delete shape; continue; } object = shape; break; } if (!object) { object = createPath(element); } return object; } -KoShape * SvgParser::createShape(const QString &shapeID) +KoShape *SvgParser::createShape(const QString &shapeID) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID); if (!factory) { debugFlake << "Could not find factory for shape id" << shapeID; return 0; } KoShape *shape = factory->createDefaultShape(m_documentResourceManager); if (!shape) { debugFlake << "Could not create Default shape for shape id" << shapeID; return 0; } if (shape->shapeId().isEmpty()) shape->setShapeId(factory->id()); // reset tranformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); return shape; } void SvgParser::applyId(const QString &id, KoShape *shape) { if (id.isEmpty()) return; shape->setName(id); m_context.registerShape(id, shape); } diff --git a/libs/flake/svg/SvgParser.h b/libs/flake/svg/SvgParser.h index 8cf5367143..f77dfac595 100644 --- a/libs/flake/svg/SvgParser.h +++ b/libs/flake/svg/SvgParser.h @@ -1,171 +1,205 @@ /* This file is part of the KDE project * Copyright (C) 2002-2003,2005 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005,2007-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 SVGPARSER_H #define SVGPARSER_H #include #include #include #include #include #include #include "kritaflake_export.h" #include "SvgGradientHelper.h" #include "SvgFilterHelper.h" #include "SvgClipPathHelper.h" #include "SvgLoadingContext.h" #include "SvgStyleParser.h" #include "KoClipMask.h" - +#include class KoShape; class KoShapeGroup; class KoDocumentResourceManager; class KoVectorPatternBackground; class KoMarker; class KoPathShape; class KRITAFLAKE_EXPORT SvgParser { public: explicit SvgParser(KoDocumentResourceManager *documentResourceManager); virtual ~SvgParser(); /// Parses a svg fragment, returning the list of top level child shapes QList parseSvg(const KoXmlElement &e, QSizeF * fragmentSize = 0); /// Sets the initial xml base directory (the directory form where the file is read) void setXmlBaseDir(const QString &baseDir); void setResolution(const QRectF boundsInPixels, qreal pixelsPerInch); /// Returns the list of all shapes of the svg document QList shapes() const; + /// Takes the collection of symbols contained in the svg document. The parser will + /// no longer know about the symbols. + QVector takeSymbols(); + typedef std::function FileFetcherFunc; void setFileFetcher(FileFetcherFunc func); QList> knownMarkers() const; + QString documentTitle() const; + QString documentDescription() const; + protected: /// Parses a group-like element element, saving all its topmost properties KoShape* parseGroup(const KoXmlElement &e, const KoXmlElement &overrideChildrenFrom = KoXmlElement()); + /// Parses a container element, returning a list of child shapes QList parseContainer(const KoXmlElement &); + + /// XXX QList parseSingleElement(const KoXmlElement &b); + /// Parses a use element, returning a list of child shapes KoShape* parseUse(const KoXmlElement &); + /// Parses a gradient element SvgGradientHelper *parseGradient(const KoXmlElement &); + /// Parses a pattern element QSharedPointer parsePattern(const KoXmlElement &e, const KoShape *__shape); + /// Parses a filter element bool parseFilter(const KoXmlElement &, const KoXmlElement &referencedBy = KoXmlElement()); + /// Parses a clip path element bool parseClipPath(const KoXmlElement &); bool parseClipMask(const KoXmlElement &e); + bool parseMarker(const KoXmlElement &e); + + bool parseSymbol(const KoXmlElement &e); + /// parses a length attribute qreal parseUnit(const QString &, bool horiz = false, bool vert = false, const QRectF &bbox = QRectF()); + /// parses a length attribute in x-direction qreal parseUnitX(const QString &unit); + /// parses a length attribute in y-direction qreal parseUnitY(const QString &unit); + /// parses a length attribute in xy-direction qreal parseUnitXY(const QString &unit); + /// parses a angular attribute values, result in radians qreal parseAngular(const QString &unit); KoShape *createObjectDirect(const KoXmlElement &b); /// Creates an object from the given xml element KoShape * createObject(const KoXmlElement &, const SvgStyles &style = SvgStyles()); + /// Create path object from the given xml element KoShape * createPath(const KoXmlElement &); + /// find gradient with given id in gradient map SvgGradientHelper* findGradient(const QString &id); + /// find pattern with given id in pattern map QSharedPointer findPattern(const QString &id, const KoShape *shape); + /// find filter with given id in filter map SvgFilterHelper* findFilter(const QString &id, const QString &href = QString()); + /// find clip path with given id in clip path map SvgClipPathHelper* findClipPath(const QString &id); /// Adds list of shapes to the given group shape void addToGroup(QList shapes, KoShapeGroup * group); /// creates a shape from the given shape id KoShape * createShape(const QString &shapeID); + /// Creates shape from specified svg element KoShape * createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context); /// Builds the document from the given shapes list void buildDocument(QList shapes); void uploadStyleToContext(const KoXmlElement &e); void applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates); /// Applies styles to the given shape void applyStyle(KoShape *, const KoXmlElement &, const QPointF &shapeToOriginalUserCoordinates); /// Applies styles to the given shape void applyStyle(KoShape *, const SvgStyles &, const QPointF &shapeToOriginalUserCoordinates); /// Applies the current fill style to the object void applyFillStyle(KoShape * shape); /// Applies the current stroke style to the object void applyStrokeStyle(KoShape * shape); /// Applies the current filter to the object void applyFilter(KoShape * shape); /// Applies the current clip path to the object void applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates); void applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates); void applyMarkers(KoPathShape *shape); /// Applies id to specified shape void applyId(const QString &id, KoShape *shape); /// Applies viewBox transformation to the current graphical context /// NOTE: after applying the function currectBoundingBox can become null! void applyViewBoxTransform(const KoXmlElement &element); private: QSizeF m_documentSize; SvgLoadingContext m_context; QMap m_gradients; QMap m_filters; QMap m_clipPaths; QMap> m_clipMasks; QMap> m_markers; KoDocumentResourceManager *m_documentResourceManager; QList m_shapes; + QVector m_symbols; QList m_toplevelShapes; + + QString m_documentTitle; + QString m_documentDescription; + }; #endif diff --git a/libs/flake/svg/SvgShape.h b/libs/flake/svg/SvgShape.h index 70f11ad1ae..26d1d042e1 100644 --- a/libs/flake/svg/SvgShape.h +++ b/libs/flake/svg/SvgShape.h @@ -1,42 +1,42 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SVGSHAPE_H #define SVGSHAPE_H #include "kritaflake_export.h" class SvgSavingContext; class SvgLoadingContext; -class KoXmlElement; +#include /// An interface providing svg loading and saving routines class KRITAFLAKE_EXPORT SvgShape { public: virtual ~SvgShape(); /// Saves data utilizing specified svg saving context virtual bool saveSvg(SvgSavingContext &context); /// Loads data from specified svg element virtual bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context); }; #endif // SVGSHAPE_H diff --git a/libs/flake/svg/SvgStyleParser.h b/libs/flake/svg/SvgStyleParser.h index c5439e3294..93a0c8eb4f 100644 --- a/libs/flake/svg/SvgStyleParser.h +++ b/libs/flake/svg/SvgStyleParser.h @@ -1,78 +1,78 @@ /* This file is part of the KDE project * Copyright (C) 2002-2003,2005 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005,2007-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 SVGSTYLEPARSER_H #define SVGSTYLEPARSER_H #include "kritaflake_export.h" #include #include typedef QMap SvgStyles; class SvgLoadingContext; class SvgGraphicsContext; -class KoXmlElement; +#include class QColor; class QGradient; class KRITAFLAKE_EXPORT SvgStyleParser { public: explicit SvgStyleParser(SvgLoadingContext &context); ~SvgStyleParser(); /// Parses specified style attributes void parseStyle(const SvgStyles &styles); /// Parses font attributes void parseFont(const SvgStyles &styles); /// Parses a color attribute bool parseColor(QColor &, const QString &); /// Parses gradient color stops void parseColorStops(QGradient *, const KoXmlElement &, SvgGraphicsContext *context, const QGradientStops &defaultStops); /// Creates style map from given xml element SvgStyles collectStyles(const KoXmlElement &); /// Merges two style elements, returning the merged style SvgStyles mergeStyles(const SvgStyles &, const SvgStyles &); /// Merges two style elements, returning the merged style SvgStyles mergeStyles(const KoXmlElement &, const KoXmlElement &); SvgStyles parseOneCssStyle(const QString &style, const QStringList &interestingAttributes); private: /// Parses a single style attribute void parsePA(SvgGraphicsContext *, const QString &, const QString &); /// Returns inherited attribute value for specified element QString inheritedAttribute(const QString &attributeName, const KoXmlElement &e); class Private; Private * const d; }; #endif // SVGSTYLEPARSER_H diff --git a/libs/flake/svg/SvgUtil.h b/libs/flake/svg/SvgUtil.h index 5b16cb3c25..793d76ee7d 100644 --- a/libs/flake/svg/SvgUtil.h +++ b/libs/flake/svg/SvgUtil.h @@ -1,141 +1,141 @@ /* 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 SvgGraphicsContext; class QTransform; -class KoXmlElement; +#include 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 double toPercentage(QString s); /** * 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); /// 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); struct 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/flake/KoSvgPaste.h b/libs/global/KisReadWriteLockPolicy.h similarity index 52% copy from libs/flake/KoSvgPaste.h copy to libs/global/KisReadWriteLockPolicy.h index 98400cf6e1..565e8c4ec3 100644 --- a/libs/flake/KoSvgPaste.h +++ b/libs/global/KisReadWriteLockPolicy.h @@ -1,38 +1,50 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef KOSVGPASTE_H -#define KOSVGPASTE_H +#ifndef KISREADWRITELOCKPOLICY_H +#define KISREADWRITELOCKPOLICY_H -#include "kritaflake_export.h" -#include +#include -class KoShape; -class QRectF; -class QSizeF; +#include +#include +#include "KisUpgradeToWriteLocker.h" -class KRITAFLAKE_EXPORT KoSvgPaste -{ -public: - KoSvgPaste(); +struct NormalLockPolicy { + typedef QReadLocker ReadLocker; + typedef QWriteLocker WriteLocker; +}; + +struct UpgradeLockPolicy { + struct FakeLocker : private boost::noncopyable { + FakeLocker(QReadWriteLock *) {} + }; - bool hasShapes() const; - QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const; + typedef FakeLocker ReadLocker; + typedef KisUpgradeToWriteLocker WriteLocker; }; -#endif // KOSVGPASTE_H +struct NoLockPolicy { + struct FakeLocker : private boost::noncopyable { + FakeLocker(QReadWriteLock *) {} + }; + + typedef FakeLocker ReadLocker; + typedef FakeLocker WriteLocker; +}; +#endif // KISREADWRITELOCKPOLICY_H diff --git a/libs/global/KisUpgradeToWriteLocker.h b/libs/global/KisUpgradeToWriteLocker.h new file mode 100644 index 0000000000..edecf80fb6 --- /dev/null +++ b/libs/global/KisUpgradeToWriteLocker.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KISUPGRADETOWRITELOCKER_H +#define KISUPGRADETOWRITELOCKER_H + +#include +#include + +/** + * @brief The KisUpgradeToWriteLocker class is use for RAII style unlocking + * the read lock and then locking the lock for write. We basically "upgrade" + * the lock to a write one. + * + * WARNING: during the *upgrade* the lock passes the "unlocked" state, so + * all the protected data you acquired during the "read" phase might + * have become invalidated! + */ +class KisUpgradeToWriteLocker +{ +public: + KisUpgradeToWriteLocker(QReadLocker *locker) + : m_locker(locker) + { + m_locker->unlock(); + m_locker->readWriteLock()->lockForWrite(); + } + + ~KisUpgradeToWriteLocker() { + m_locker->readWriteLock()->unlock(); + m_locker->relock(); + } + +private: + QReadLocker *m_locker; +}; + + + +#endif // KISUPGRADETOWRITELOCKER_H diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp index a2e482dda4..d6cb78d09c 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,420 +1,420 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_base_node.h" #include #include #include #include #include #include "kis_paint_device.h" #include "kis_layer_properties_icons.h" #include "kis_scalar_keyframe_channel.h" struct Q_DECL_HIDDEN KisBaseNode::Private { QString compositeOp; KoProperties properties; KisBaseNode::Property hack_visible; //HACK QUuid id; QMap keyframeChannels; QScopedPointer opacityChannel; bool systemLocked; bool collapsed; bool supportsLodMoves; bool animated; bool useInTimeline; Private() : id(QUuid::createUuid()) , systemLocked(false) , collapsed(false) , supportsLodMoves(false) , animated(false) , useInTimeline(false) { } Private(const Private &rhs) : compositeOp(rhs.compositeOp), id(QUuid::createUuid()), systemLocked(false), collapsed(rhs.collapsed), supportsLodMoves(rhs.supportsLodMoves), animated(rhs.animated), useInTimeline(rhs.useInTimeline) { QMapIterator iter = rhs.properties.propertyIterator(); while (iter.hasNext()) { iter.next(); properties.setProperty(iter.key(), iter.value()); } } }; KisBaseNode::KisBaseNode() : m_d(new Private()) { /** * Be cautious! These two calls are vital to warm-up KoProperties. * We use it and its QMap in a threaded environment. This is not * officially suported by Qt, but our environment guarantees, that * there will be the only writer and several readers. Whilst the * value of the QMap is boolean and there are no implicit-sharing * calls provocated, it is safe to work with it in such an * environment. */ setVisible(true, true); setUserLocked(false); setCollapsed(false); setSupportsLodMoves(true); m_d->compositeOp = COMPOSITE_OVER; } KisBaseNode::KisBaseNode(const KisBaseNode & rhs) : QObject() , KisShared() , m_d(new Private(*rhs.m_d)) { } KisBaseNode::~KisBaseNode() { delete m_d; } quint8 KisBaseNode::opacity() const { if (m_d->opacityChannel) { qreal value = m_d->opacityChannel->currentValue(); if (!qIsNaN(value)) { return value; } } return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8); } void KisBaseNode::setOpacity(quint8 val) { if (m_d->opacityChannel) { KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe(); if (activeKeyframe) { m_d->opacityChannel->setScalarValue(activeKeyframe, val); } } if (opacity() == val) return; nodeProperties().setProperty("opacity", val); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } quint8 KisBaseNode::percentOpacity() const { return int(float(opacity() * 100) / 255 + 0.5); } void KisBaseNode::setPercentOpacity(quint8 val) { setOpacity(int(float(val * 255) / 100 + 0.5)); } const QString& KisBaseNode::compositeOpId() const { return m_d->compositeOp; } void KisBaseNode::setCompositeOpId(const QString& compositeOp) { if (m_d->compositeOp == compositeOp) return; m_d->compositeOp = compositeOp; baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const { KisBaseNode::PropertyList l; l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked()); return l; } void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { setVisible(properties.at(0).state.toBool()); m_d->hack_visible = properties.at(0); setUserLocked(properties.at(1).state.toBool()); } KoProperties & KisBaseNode::nodeProperties() const { return m_d->properties; } void KisBaseNode::mergeNodeProperties(const KoProperties & properties) { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); m_d->properties.setProperty(iter.key(), iter.value()); } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } bool KisBaseNode::check(const KoProperties & properties) const { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); if (m_d->properties.contains(iter.key())) { if (m_d->properties.value(iter.key()) != iter.value()) return false; } } return true; } QImage KisBaseNode::createThumbnail(qint32 w, qint32 h) { try { QImage image(w, h, QImage::Format_ARGB32); image.fill(0); return image; } catch (std::bad_alloc) { return QImage(); } } QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time) { Q_UNUSED(time) return createThumbnail(w, h); } bool KisBaseNode::visible(bool recursive) const { bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); KisBaseNodeSP parentNode = parentCallback(); return recursive && isVisible && parentNode ? parentNode->visible() : isVisible; } void KisBaseNode::setVisible(bool visible, bool loading) { const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); if (!loading && isVisible == visible) return; m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible); notifyParentVisibilityChanged(visible); if (!loading) { emit visibilityChanged(visible); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } bool KisBaseNode::userLocked() const { return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false); } void KisBaseNode::setUserLocked(bool locked) { const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true); if (isLocked == locked) return; m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked); emit userLockingChanged(locked); baseNodeChangedCallback(); } bool KisBaseNode::isEditable(bool checkVisibility) const { bool editable = true; if (checkVisibility) { editable = (visible(false) && !userLocked()); } else { editable = (!userLocked()); } if (editable) { KisBaseNodeSP parentNode = parentCallback(); if (parentNode && parentNode != this) { editable = parentNode->isEditable(checkVisibility); } } return editable; } bool KisBaseNode::hasEditablePaintDevice() const { return paintDevice() && isEditable(); } void KisBaseNode::setCollapsed(bool collapsed) { m_d->collapsed = collapsed; } bool KisBaseNode::collapsed() const { return m_d->collapsed; } void KisBaseNode::setColorLabelIndex(int index) { const int currentLabel = colorLabelIndex(); if (currentLabel == index) return; m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index); baseNodeChangedCallback(); } int KisBaseNode::colorLabelIndex() const { return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0); } QUuid KisBaseNode::uuid() const { return m_d->id; } void KisBaseNode::setUuid(const QUuid& id) { m_d->id = id; baseNodeChangedCallback(); } bool KisBaseNode::supportsLodMoves() const { return m_d->supportsLodMoves; } void KisBaseNode::setImage(KisImageWSP image) { Q_UNUSED(image); } void KisBaseNode::setSupportsLodMoves(bool value) { m_d->supportsLodMoves = value; } -QList KisBaseNode::keyframeChannels() const +QMap KisBaseNode::keyframeChannels() const { - return m_d->keyframeChannels.values(); + return m_d->keyframeChannels; } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const { QMap::const_iterator i = m_d->keyframeChannels.constFind(id); if (i == m_d->keyframeChannels.constEnd()) { return 0; } return i.value(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create) { KisKeyframeChannel *channel = getKeyframeChannel(id); if (!channel && create) { channel = requestKeyframeChannel(id); if (channel) { addKeyframeChannel(channel); } } return channel; } bool KisBaseNode::isAnimated() const { return m_d->animated; } void KisBaseNode::enableAnimation() { m_d->animated = true; baseNodeChangedCallback(); } bool KisBaseNode::useInTimeline() const { return m_d->useInTimeline; } void KisBaseNode::setUseInTimeline(bool value) { if (value == m_d->useInTimeline) return; m_d->useInTimeline = value; baseNodeChangedCallback(); } void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel) { m_d->keyframeChannels.insert(channel->id(), channel); emit keyframeChannelAdded(channel); } KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Opacity.id()) { Q_ASSERT(m_d->opacityChannel.isNull()); KisPaintDeviceSP device = original(); if (device) { KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel( KisKeyframeChannel::Opacity, 0, 255, device->defaultBounds(), KisKeyframe::Linear ); m_d->opacityChannel.reset(channel); return channel; } } return 0; } diff --git a/libs/image/kis_base_node.h b/libs/image/kis_base_node.h index 5ee2b50ddc..dd795caaec 100644 --- a/libs/image/kis_base_node.h +++ b/libs/image/kis_base_node.h @@ -1,565 +1,565 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_BASE_NODE_H #define _KIS_BASE_NODE_H #include #include #include #include #include #include "kis_shared.h" #include "kis_paint_device.h" #include "kis_processing_visitor.h" // included, not forward declared for msvc class KoProperties; class KoColorSpace; class KoCompositeOp; class KisNodeVisitor; class KisUndoAdapter; class KisKeyframeChannel; #include "kritaimage_export.h" /** * A KisBaseNode is the base class for all components of an image: * nodes, layers masks, selections. A node has a number of properties, * can be represented as a thumbnail and knows what to do when it gets * a certain paint device to process. A KisBaseNode does not know * anything about its peers. You should not directly inherit from a * KisBaseNode; inherit from KisNode instead. */ class KRITAIMAGE_EXPORT KisBaseNode : public QObject, public KisShared { Q_OBJECT public: /** * Describes a property of a document section. * * FIXME: using a QList instead of QMap and not having an untranslated identifier, * either enum or string, forces applications to rely on the order of properties * or to compare the translated strings. This makes it hard to robustly extend the * properties of document section items. */ struct Property { QString id; /** i18n-ed name, suitable for displaying */ QString name; /** Whether the property is a boolean (e.g. locked, visible) which can be toggled directly from the widget itself. */ bool isMutable; /** Provide these if the property isMutable. */ QIcon onIcon; QIcon offIcon; /** If the property isMutable, provide a boolean. Otherwise, a string suitable for displaying. */ QVariant state; /** If the property is mutable, specifies whether it can be put into stasis. When a property is in stasis, a new state is created, and the old one is stored in stateInStasis. When stasis ends, the old value is restored and the new one discarded */ bool canHaveStasis; /** If the property isMutable and canHaveStasis, indicate whether it is in stasis or not */ bool isInStasis; /** If the property isMutable and canHaveStasis, provide this value to store the property's state while in stasis */ bool stateInStasis; bool operator==(const Property &rhs) const { return rhs.name == name; } Property(): isMutable( false ) { } /// Constructor for a mutable property. Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn ) : id(n.id()), name( n.name() ), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( false ) { } /** Constructor for a mutable property accepting stasis */ Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn, bool _isInStasis, bool _stateInStasis ) : id(n.id()), name(n.name()), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( true ), isInStasis( _isInStasis ), stateInStasis( _stateInStasis ) { } /// Constructor for a nonmutable property. Property( const KoID &n, const QString &s ) : id(n.id()), name(n.name()), isMutable( false ), state( s ) { } }; /** Return this type for PropertiesRole. */ typedef QList PropertyList; public: /** * Create a new, empty base node. The node is unnamed, unlocked * visible and unlinked. */ KisBaseNode(); /** * Create a copy of this node. */ KisBaseNode(const KisBaseNode & rhs); /** * Delete this node */ ~KisBaseNode() override; /** * Return the paintdevice you can use to change pixels on. For a * paint layer these will be paint pixels, for an adjustment layer or a mask * the selection paint device. * * @return the paint device to paint on. Can be 0 if the actual * node type does not support painting. */ virtual KisPaintDeviceSP paintDevice() const = 0; /** * @return the rendered representation of a node * before the effect masks have had their go at it. Can be 0. */ virtual KisPaintDeviceSP original() const = 0; /** * @return the fully rendered representation of this layer: its * rendered original and its effect masks. Can be 0. */ virtual KisPaintDeviceSP projection() const = 0; virtual const KoColorSpace *colorSpace() const = 0; /** * Return the opacity of this layer, scaled to a range between 0 * and 255. * XXX: Allow true float opacity */ quint8 opacity() const; //0-255 /** * Set the opacity for this layer. The range is between 0 and 255. * The layer will be marked dirty. * * XXX: Allow true float opacity */ void setOpacity(quint8 val); //0-255 /** * return the 8-bit opacity of this layer scaled to the range * 0-100 * * XXX: Allow true float opacity */ quint8 percentOpacity() const; //0-100 /** * Set the opacity of this layer with a number between 0 and 100; * the number will be scaled to between 0 and 255. * XXX: Allow true float opacity */ void setPercentOpacity(quint8 val); //0-100 /** * Return the composite op associated with this layer. */ virtual const KoCompositeOp *compositeOp() const = 0; const QString& compositeOpId() const; /** * Set a new composite op for this layer. The layer will be marked * dirty. */ void setCompositeOpId(const QString& compositeOpId); /** * @return unique id, which is now used by clone layers. */ QUuid uuid() const; /** * Set the uuid of node. This should only be used when loading * existing node and in constructor. */ void setUuid(const QUuid& id); /** * return the name of this node. This is the same as the * QObject::objectName. */ QString name() const { return objectName(); } /** * set the QObject::objectName. This is also the user-visible name * of the layer. The reason for this is that we want to see the * layer name also when debugging. */ void setName(const QString& name) { setObjectName(name); baseNodeChangedCallback(); } /** * @return the icon used to represent the node type, for instance * in the layerbox and in the menu. */ virtual QIcon icon() const { return QIcon(); } /** * Return a the properties of this base node (locked, visible etc, * with the right icons for their representation and their state. * * Subclasses can extend this list with new properties, like * opacity for layers or visualized for masks. * * The order of properties is, unfortunately, for now, important, * so take care which properties superclasses of your class * define. * * KisBaseNode defines visible = 0, locked = 1 * KisLayer defines opacity = 2, compositeOp = 3 * KisMask defines active = 2 (KisMask does not inherit kislayer) */ virtual PropertyList sectionModelProperties() const; /** * Change the section model properties. */ virtual void setSectionModelProperties(const PropertyList &properties); /** * Return all the properties of this layer as a KoProperties-based * serializable key-value list. */ KoProperties & nodeProperties() const; /** * Merge the specified properties with the properties of this * layer. Whereever these properties overlap, the value of the * node properties is changed. No properties on the node are * deleted. If there are new properties in this list, they will be * added on the node. */ void mergeNodeProperties(const KoProperties & properties); /** * Compare the given properties list with the properties of this * node. * * @return false only if the same property exists in both lists * but with a different value. Properties that are not in both * lists are disregarded. */ bool check(const KoProperties & properties) const; /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisNodeVisitor for this * node type, so you need to override it for all leaf classes in * the node inheritance hierarchy. * * return false if the visitor could not successfully act on this * node instance. */ virtual bool accept(KisNodeVisitor &) { return false; } /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisProcessingVisitor * for this node type, so you need to override it for all leaf * classes in the node inheritance hierarchy. * * The processing visitor differs from node visitor in the way * that it accepts undo adapter, that allows the processing to * be multithreaded */ virtual void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { Q_UNUSED(visitor); Q_UNUSED(undoAdapter); } /** * @return a thumbnail in requested size. The thumbnail is a rgba * QImage and may have transparent parts. Returns a fully * transparent QImage of the requested size if the current node * type cannot generate a thumbnail. If the requested size is too * big, return a null QImage. */ virtual QImage createThumbnail(qint32 w, qint32 h); /** * @return a thumbnail in requested size for the defined timestamp. * The thumbnail is a rgba Image and may have transparent parts. * Returns a fully transparent QImage of the requested size if the * current node type cannot generate a thumbnail. If the requested * size is too big, return a null QImage. */ virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time); /** * Ask this node to re-read the pertinent settings from the krita * configuration. */ virtual void updateSettings() { } /** * @return true if this node is visible (i.e, active (except for * selection masks where visible and active properties are * different)) in the graph */ virtual bool visible(bool recursive = false) const; /** * Set the visible status of this node. Visible nodes are active * in the graph (except for selections masks which can be active * while hidden), that is to say, they are taken into account * when merging. Invisible nodes play no role in the final image *, but will be modified when modifying all layers, for instance * when cropping. * * Toggling the visibility of a node will not automatically lead * to recomposition. * * @param visible the new visibility state * @param isLoading if true, the property is set during loading. */ virtual void setVisible(bool visibile, bool loading = false); /** * Return the locked status of this node. Locked nodes cannot be * edited. */ bool userLocked() const; /** * Set the locked status of this node. Locked nodes cannot be * edited. */ void setUserLocked(bool l); /** * @return true if the node can be edited: * * if checkVisibility is true, then the node is only editable if it is visible and not locked. * if checkVisibility is false, then the node is editable if it's not locked. */ bool isEditable(bool checkVisibility = true) const; /** * @return true if the node is editable and has a paintDevice() * which which can be used for accessing pixels. It is an * equivalent to (isEditable() && paintDevice()) */ bool hasEditablePaintDevice() const; /** * @return the x-offset of this layer in the image plane. */ virtual qint32 x() const { return 0; } /** * Set the x offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setX(qint32) { } /** * @return the y-offset of this layer in the image plane. */ virtual qint32 y() const { return 0; } /** * Set the y offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setY(qint32) { } /** * Returns an approximation of where the bounds on actual data are * in this node. */ virtual QRect extent() const { return QRect(); } /** * Returns the exact bounds of where the actual data resides in * this node. */ virtual QRect exactBounds() const { return QRect(); } /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ void setColorLabelIndex(int index); /** * \see setColorLabelIndex */ int colorLabelIndex() const; /** * Returns true if the offset of the node can be changed in a LodN * stroke. Currently, all the nodes except shape layers support that. */ bool supportsLodMoves() const; /** * Return the keyframe channels associated with this node * @return list of keyframe channels */ - QList keyframeChannels() const; + QMap keyframeChannels() const; /** * Get the keyframe channel with given id. * If the channel does not yet exist and the node supports the requested * channel, it will be created if create is true. * @param id internal name for channel * @param create attempt to create the channel if it does not exist yet * @return keyframe channel with the id, or null if not found */ KisKeyframeChannel *getKeyframeChannel(const QString &id, bool create); KisKeyframeChannel *getKeyframeChannel(const QString &id) const; bool useInTimeline() const; void setUseInTimeline(bool value); bool isAnimated() const; virtual void enableAnimation(); virtual void setImage(KisImageWSP image); protected: void setSupportsLodMoves(bool value); /** * FIXME: This method is a workaround for getting parent node * on a level of KisBaseNode. In fact, KisBaseNode should inherit * KisNode (in terms of current Krita) to be able to traverse * the node stack */ virtual KisBaseNodeSP parentCallback() const { return KisBaseNodeSP(); } virtual void notifyParentVisibilityChanged(bool value) { Q_UNUSED(value); } /** * This callback is called when some meta state of the base node * that can be interesting to the UI has changed. E.g. visibility, * lockness, opacity, compositeOp and etc. This signal is * forwarded by the KisNode and KisNodeGraphListener to the model * in KisLayerBox, so it can update its controls when information * changes. */ virtual void baseNodeChangedCallback() { } virtual void baseNodeInvalidateAllFramesCallback() { } /** * Add a keyframe channel for this node. The channel will be added * to the common hash table which will be available to the UI. * * WARNING: the \p channel object *NOT* become owned by the node! * The caller must ensure manually that the lifetime of * the object coincide with the lifetime of the node. */ virtual void addKeyframeChannel(KisKeyframeChannel* channel); /** * Attempt to create the requested channel. Used internally by getKeyframeChannel. * Subclasses should implement this method to catch any new channel types they support. * @param id channel to create * @return newly created channel or null */ virtual KisKeyframeChannel * requestKeyframeChannel(const QString &id); Q_SIGNALS: /** * This signal is emitted when the visibility of the layer is changed with \ref setVisible. */ void visibilityChanged(bool); /** * This signal is emitted when the node is locked or unlocked with \ref setUserLocked. */ void userLockingChanged(bool); void keyframeChannelAdded(KisKeyframeChannel *channel); private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE( KisBaseNode::PropertyList ) #endif diff --git a/libs/image/kis_filter_strategy.cc b/libs/image/kis_filter_strategy.cc index 06dc41e39b..5dc92e064a 100644 --- a/libs/image/kis_filter_strategy.cc +++ b/libs/image/kis_filter_strategy.cc @@ -1,238 +1,238 @@ /* * Copyright (c) 2004 Michael Thaler * Copyright (c) 2005 C. Boemann * Copyright (c) 2013 Juan Palacios * * 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_filter_strategy.h" #include #include #include #include "kis_debug.h" Q_GLOBAL_STATIC(KisFilterStrategyRegistry, s_instance) qreal KisHermiteFilterStrategy::valueAt(qreal t) const { /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ if (t < 0.0) t = -t; if (t < 1.0) return((2.0 * t - 3.0) * t * t + 1.0); return(0.0); } qint32 KisHermiteFilterStrategy::intValueAt(qint32 t) const { /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ if (t < 0) t = -t; if (t < 256) { t = (2 * t - 3 * 256) * t * t + (256 << 16); //go from .24 fixed point to .8 fixedpoint (hack only works with positve numbers, which it is) t = (t + 0x8000) >> 16; // go from .8 fixed point to 8bitscale. ie t = (t*255)/256; if (t >= 128) return t - 1; return t; } return(0); } qint32 KisBicubicFilterStrategy::intValueAt(qint32 t) const { /* f(t) = 1.5|t|^3 - 2.5|t|^2 + 1, -1 <= t <= 1 */ if (t < 0) t = -t; if (t < 256) { t = (3 * t - 5 * 256) * t * t / 2 + (256 << 16); //go from .24 fixed point to .8 fixedpoint (hack only works with positve numbers, which it is) t = (t + 0x8000) >> 16; // go from .8 fixed point to 8bitscale. ie t = (t*255)/256; if (t >= 128) return t - 1; return t; } if (t < 512) { /* f(t) = -0.5|t|^3 + 2.5|t|^2 + 4|t| - 2, -2 <= t <= 2 */ t = ((-t + 5 * 256) * t / 2 - 4 * 256 * 256) * t + (2 * 256 << 16); //go from .24 fixed point to .8 fixedpoint (hack only works with positve numbers, which it is) t = (t + 0x8000) >> 16; // go from .8 fixed point to 8bitscale. ie t = (t*255)/256; if (t >= 128) return t - 1; return t; } return(0); } qreal KisBoxFilterStrategy::valueAt(qreal t) const { if ((t > -0.5) && (t <= 0.5)) return(1.0); return(0.0); } qint32 KisBoxFilterStrategy::intValueAt(qint32 t) const { /* f(t) = 1, -0.5 < t <= 0.5 */ if ((t > -128) && (t <= 128)) return 255; return 0; } qreal KisBilinearFilterStrategy::valueAt(qreal t) const { if (t < 0.0) t = -t; if (t < 1.0) return(1.0 - t); return(0.0); } qint32 KisBilinearFilterStrategy::intValueAt(qint32 t) const { /* f(t) = |t|, -1 <= t <= 1 */ if (t < 0) t = -t; if (t < 256) { // calc 256-1 but also go from .8 fixed point to 8bitscale. ie t = (t*255)/256; ie: if(t>=128) return t-1; if (t >= 128) return 256 - t; return 255 - t; } return(0); } qreal KisBellFilterStrategy::valueAt(qreal t) const { if (t < 0) t = -t; if (t < .5) return(.75 - (t * t)); if (t < 1.5) { t = (t - 1.5); return(.5 *(t * t)); } return(0.0); } qreal KisBSplineFilterStrategy::valueAt(qreal t) const { qreal tt; if (t < 0) t = -t; if (t < 1) { tt = t * t; return((.5 * tt * t) - tt + (2.0 / 3.0)); } else if (t < 2) { t = 2 - t; return((1.0 / 6.0) *(t * t * t)); } return(0.0); } qreal KisLanczos3FilterStrategy::valueAt(qreal t) const { if (t < 0) t = -t; if (t < 3.0) return(sinc(t) * sinc(t / 3.0)); return(0.0); } qreal KisLanczos3FilterStrategy::sinc(qreal x) const { const qreal pi = 3.1415926535897932385; x *= pi; if (x != 0) return(sin(x) / x); return(1.0); } qreal KisMitchellFilterStrategy::valueAt(qreal t) const { const qreal B = 1.0 / 3.0; const qreal C = 1.0 / 3.0; qreal tt; tt = t * t; if (t < 0) t = -t; if (t < 1.0) { t = (((12.0 - 9.0 * B - 6.0 * C) * (t * tt)) + ((-18.0 + 12.0 * B + 6.0 * C) * tt) + (6.0 - 2 * B)); return(t / 6.0); } else if (t < 2.0) { t = (((-1.0 * B - 6.0 * C) * (t * tt)) + ((6.0 * B + 30.0 * C) * tt) + ((-12.0 * B - 48.0 * C) * t) + (8.0 * B + 24 * C)); return(t / 6.0); } return(0.0); } KisFilterStrategyRegistry::KisFilterStrategyRegistry() { } KisFilterStrategyRegistry::~KisFilterStrategyRegistry() { Q_FOREACH (const QString &id, keys()) { delete get(id); - } + } dbgRegistry << "deleting KisFilterStrategyRegistry"; } KisFilterStrategyRegistry* KisFilterStrategyRegistry::instance() { if (!s_instance.exists()) { - // s_instance->add(new KisHermiteFilterStrategy); + s_instance->add(new KisHermiteFilterStrategy); s_instance->add(new KisBicubicFilterStrategy); s_instance->add(new KisBoxFilterStrategy); s_instance->add(new KisBilinearFilterStrategy); - // s_instance->add(new KisBellFilterStrategy); - // s_instance->add(new KisBSplineFilterStrategy); + s_instance->add(new KisBellFilterStrategy); + s_instance->add(new KisBSplineFilterStrategy); s_instance->add(new KisLanczos3FilterStrategy); - // s_instance->add(new KisMitchellFilterStrategy); + s_instance->add(new KisMitchellFilterStrategy); } return s_instance; } QList KisFilterStrategyRegistry::listKeys() const { QList answer; Q_FOREACH (const QString key, keys()) { answer.append(KoID(key, get(key)->name())); } return answer; } -QString KisFilterStrategyRegistry::formatedDescriptions() const +QString KisFilterStrategyRegistry::formattedDescriptions() const { QString formatedDescription(""); Q_FOREACH (const QString key, keys()) { KisFilterStrategy *strategy = get(key); QString description = strategy->description(); if (!description.isEmpty()) { formatedDescription.append("

"); formatedDescription.append(strategy->name()); formatedDescription.append(": "); formatedDescription.append(description); formatedDescription.append("

"); } } formatedDescription.append(""); return formatedDescription; } diff --git a/libs/image/kis_filter_strategy.h b/libs/image/kis_filter_strategy.h index 1cd5e36daf..68ea3029ad 100644 --- a/libs/image/kis_filter_strategy.h +++ b/libs/image/kis_filter_strategy.h @@ -1,206 +1,206 @@ /* * Copyright (c) 2004 Michael Thaler * Copyright (c) 2005 C. Boemann * Copyright (c) 2013 Juan Palacios * * 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_FILTER_STRATEGY_H_ #define KIS_FILTER_STRATEGY_H_ #include #include "KoGenericRegistry.h" #include "KoID.h" #include "kritaimage_export.h" class KRITAIMAGE_EXPORT KisFilterStrategy { public: KisFilterStrategy(KoID id) : m_id(id) {} - virtual ~KisFilterStrategy() {} + virtual ~KisFilterStrategy() { } QString id() { return m_id.id(); } QString name() { return m_id.name(); } virtual qreal valueAt(qreal /*t*/) const { return 0; } virtual qint32 intValueAt(qint32 t) const { return qint32(255*valueAt(t / 256.0)); } qreal support() { return supportVal; } qint32 intSupport() { return intSupportVal; } virtual bool boxSpecial() { return false; } virtual QString description() { return QString(); } protected: qreal supportVal; qint32 intSupportVal; KoID m_id; }; class KRITAIMAGE_EXPORT KisHermiteFilterStrategy : public KisFilterStrategy { public: KisHermiteFilterStrategy() : KisFilterStrategy(KoID("Hermite", i18n("Hermite"))) { supportVal = 1.0; intSupportVal = 256; } ~KisHermiteFilterStrategy() override {} qint32 intValueAt(qint32 t) const override; qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisBicubicFilterStrategy : public KisFilterStrategy { public: KisBicubicFilterStrategy() : KisFilterStrategy(KoID("Bicubic", i18n("Bicubic"))) { supportVal = 2.0; intSupportVal = 512; } ~KisBicubicFilterStrategy() override {} QString description() override { return i18n("Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear."); } qint32 intValueAt(qint32 t) const override; }; class KRITAIMAGE_EXPORT KisBoxFilterStrategy : public KisFilterStrategy { public: KisBoxFilterStrategy() : KisFilterStrategy(KoID("Box", i18n("Box"))) { supportVal = 0.5; intSupportVal = 128; } ~KisBoxFilterStrategy() override {} QString description() override { return i18n("Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects."); } qint32 intValueAt(qint32 t) const override; qreal valueAt(qreal t) const override; bool boxSpecial() override { return true; } }; class KRITAIMAGE_EXPORT KisBilinearFilterStrategy : public KisFilterStrategy { public: KisBilinearFilterStrategy() : KisFilterStrategy(KoID("Bilinear", i18n("Bilinear"))) { supportVal = 1.0; intSupportVal = 256; } ~KisBilinearFilterStrategy() override {} QString description() override { return i18n("Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size."); } qint32 intValueAt(qint32 t) const override; qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisBellFilterStrategy : public KisFilterStrategy { public: KisBellFilterStrategy() : KisFilterStrategy(KoID("Bell", i18n("Bell"))) { supportVal = 1.5; intSupportVal = 128 + 256; } ~KisBellFilterStrategy() override {} qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisBSplineFilterStrategy : public KisFilterStrategy { public: KisBSplineFilterStrategy() : KisFilterStrategy(KoID("BSpline", i18n("BSpline"))) { supportVal = 2.0; intSupportVal = 512; } ~KisBSplineFilterStrategy() override {} qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisLanczos3FilterStrategy : public KisFilterStrategy { public: KisLanczos3FilterStrategy() : KisFilterStrategy(KoID("Lanczos3", i18n("Lanczos3"))) { supportVal = 3.0; intSupportVal = 768; } ~KisLanczos3FilterStrategy() override {} QString description() override { return i18n("Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges."); } qreal valueAt(qreal t) const override; private: qreal sinc(qreal x) const; }; class KRITAIMAGE_EXPORT KisMitchellFilterStrategy : public KisFilterStrategy { public: KisMitchellFilterStrategy() : KisFilterStrategy(KoID("Mitchell", i18n("Mitchell"))) { supportVal = 2.0; intSupportVal = 256; } ~KisMitchellFilterStrategy() override {} qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisFilterStrategyRegistry : public KoGenericRegistry { public: KisFilterStrategyRegistry(); ~KisFilterStrategyRegistry() override; static KisFilterStrategyRegistry* instance(); /** * This function return a list of all the keys in KoID format by using the name() method * on the objects stored in the registry. */ QList listKeys() const; /** * This function return a string formated in HTML that contains the descriptions of all objects * (with a non empty description) stored in the registry. */ - QString formatedDescriptions() const; + QString formattedDescriptions() const; private: KisFilterStrategyRegistry(const KisFilterStrategyRegistry&); KisFilterStrategyRegistry operator=(const KisFilterStrategyRegistry&); }; #endif // KIS_FILTER_STRATEGY_H_ diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp index 0258ccc41a..3910efc653 100644 --- a/libs/image/kis_image_config.cpp +++ b/libs/image/kis_image_config.cpp @@ -1,455 +1,455 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_config.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include "kis_global.h" #include #ifdef Q_OS_OSX #include #endif KisImageConfig::KisImageConfig(bool readOnly) : m_config( KSharedConfig::openConfig()->group(QString())), m_readOnly(readOnly) { } KisImageConfig::~KisImageConfig() { if (m_readOnly) return; if (qApp->thread() != QThread::currentThread()) { dbgKrita << "KisImageConfig: requested config synchronization from nonGUI thread! Called from" << kisBacktrace(); return; } m_config.sync(); } bool KisImageConfig::enableProgressReporting(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enableProgressReporting", true) : true; } void KisImageConfig::setEnableProgressReporting(bool value) { m_config.writeEntry("enableProgressReporting", value); } bool KisImageConfig::enablePerfLog(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enablePerfLog", false) :false; } void KisImageConfig::setEnablePerfLog(bool value) { m_config.writeEntry("enablePerfLog", value); } qreal KisImageConfig::transformMaskOffBoundsReadArea() const { return m_config.readEntry("transformMaskOffBoundsReadArea", 0.5); } int KisImageConfig::updatePatchHeight() const { return m_config.readEntry("updatePatchHeight", 512); } void KisImageConfig::setUpdatePatchHeight(int value) { m_config.writeEntry("updatePatchHeight", value); } int KisImageConfig::updatePatchWidth() const { return m_config.readEntry("updatePatchWidth", 512); } void KisImageConfig::setUpdatePatchWidth(int value) { m_config.writeEntry("updatePatchWidth", value); } qreal KisImageConfig::maxCollectAlpha() const { return m_config.readEntry("maxCollectAlpha", 2.5); } qreal KisImageConfig::maxMergeAlpha() const { return m_config.readEntry("maxMergeAlpha", 1.); } qreal KisImageConfig::maxMergeCollectAlpha() const { return m_config.readEntry("maxMergeCollectAlpha", 1.5); } qreal KisImageConfig::schedulerBalancingRatio() const { /** * updates-queue-size / strokes-queue-size */ return m_config.readEntry("schedulerBalancingRatio", 100.); } void KisImageConfig::setSchedulerBalancingRatio(qreal value) { m_config.writeEntry("schedulerBalancingRatio", value); } int KisImageConfig::maxSwapSize(bool requestDefault) const { return !requestDefault ? m_config.readEntry("maxSwapSize", 4096) : 4096; // in MiB } void KisImageConfig::setMaxSwapSize(int value) { m_config.writeEntry("maxSwapSize", value); } int KisImageConfig::swapSlabSize() const { return m_config.readEntry("swapSlabSize", 64); // in MiB } void KisImageConfig::setSwapSlabSize(int value) { m_config.writeEntry("swapSlabSize", value); } int KisImageConfig::swapWindowSize() const { return m_config.readEntry("swapWindowSize", 16); // in MiB } void KisImageConfig::setSwapWindowSize(int value) { m_config.writeEntry("swapWindowSize", value); } int KisImageConfig::tilesHardLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * (1 - pp); } int KisImageConfig::tilesSoftLimit() const { qreal sp = qreal(memorySoftLimitPercent()) / 100.0; return tilesHardLimit() * sp; } int KisImageConfig::poolLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * pp; } qreal KisImageConfig::memoryHardLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryHardLimitPercent", 50.) : 50.; } void KisImageConfig::setMemoryHardLimitPercent(qreal value) { m_config.writeEntry("memoryHardLimitPercent", value); } qreal KisImageConfig::memorySoftLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memorySoftLimitPercent", 2.) : 2.; } void KisImageConfig::setMemorySoftLimitPercent(qreal value) { m_config.writeEntry("memorySoftLimitPercent", value); } qreal KisImageConfig::memoryPoolLimitPercent(bool requestDefault) const { return !requestDefault ? - m_config.readEntry("memoryPoolLimitPercent", 2.) : 2.; + m_config.readEntry("memoryPoolLimitPercent", 0.0) : 0.0; } void KisImageConfig::setMemoryPoolLimitPercent(qreal value) { m_config.writeEntry("memoryPoolLimitPercent", value); } QString KisImageConfig::swapDir(bool requestDefault) { QString swap = QDir::tempPath(); return !requestDefault ? m_config.readEntry("swaplocation", swap) : swap; } void KisImageConfig::setSwapDir(const QString &swapDir) { m_config.writeEntry("swaplocation", swapDir); } int KisImageConfig::numberOfOnionSkins() const { return m_config.readEntry("numberOfOnionSkins", 10); } void KisImageConfig::setNumberOfOnionSkins(int value) { m_config.writeEntry("numberOfOnionSkins", value); } int KisImageConfig::onionSkinTintFactor() const { return m_config.readEntry("onionSkinTintFactor", 192); } void KisImageConfig::setOnionSkinTintFactor(int value) { m_config.writeEntry("onionSkinTintFactor", value); } int KisImageConfig::onionSkinOpacity(int offset) const { int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1); if (value < 0) { const int num = numberOfOnionSkins(); const qreal dx = qreal(qAbs(offset)) / num; value = 0.7 * exp(-pow2(dx) / 0.5) * 255; } return value; } void KisImageConfig::setOnionSkinOpacity(int offset, int value) { m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value); } bool KisImageConfig::onionSkinState(int offset) const { bool enableByDefault = (qAbs(offset) <= 2); return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault); } void KisImageConfig::setOnionSkinState(int offset, bool value) { m_config.writeEntry("onionSkinState_" + QString::number(offset), value); } QColor KisImageConfig::onionSkinTintColorBackward() const { return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red)); } void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value) { m_config.writeEntry("onionSkinTintColorBackward", value); } QColor KisImageConfig::onionSkinTintColorForward() const { return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green)); } void KisImageConfig::setOnionSkinTintColorForward(const QColor &value) { m_config.writeEntry("oninSkinTintColorForward", value); } bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const { return !requestDefault ? m_config.readEntry("lazyFrameCreationEnabled", true) : true; } void KisImageConfig::setLazyFrameCreationEnabled(bool value) { m_config.writeEntry("lazyFrameCreationEnabled", value); } #if defined Q_OS_LINUX #include #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD #include #elif defined Q_OS_WIN #include #elif defined Q_OS_OSX #include #include #endif #include int KisImageConfig::totalRAM() { // let's think that default memory size is 1000MiB int totalMemory = 1000; // MiB int error = 1; #if defined Q_OS_LINUX struct sysinfo info; error = sysinfo(&info); if(!error) { totalMemory = info.totalram * info.mem_unit / (1UL << 20); } #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD u_long physmem; # if defined HW_PHYSMEM64 // NetBSD only int mib[] = {CTL_HW, HW_PHYSMEM64}; # else int mib[] = {CTL_HW, HW_PHYSMEM}; # endif size_t len = sizeof(physmem); error = sysctl(mib, 2, &physmem, &len, 0, 0); if(!error) { totalMemory = physmem >> 20; } #elif defined Q_OS_WIN MEMORYSTATUSEX status; status.dwLength = sizeof(status); error = !GlobalMemoryStatusEx(&status); if (!error) { totalMemory = status.ullTotalPhys >> 20; } // For 32 bit windows, the total memory available is at max the 2GB per process memory limit. # if defined ENV32BIT totalMemory = qMin(totalMemory, 2000); # endif #elif defined Q_OS_OSX int mib[2] = { CTL_HW, HW_MEMSIZE }; u_int namelen = sizeof(mib) / sizeof(mib[0]); uint64_t size; size_t len = sizeof(size); errno = 0; if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) { totalMemory = size >> 20; error = 0; } else { dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno); } #endif if (error) { warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default."; } return totalMemory; } bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const { return !requestDefault ? m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true; } void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value) { m_config.writeEntry("showAdditionalOnionSkinsSettings", value); } int KisImageConfig::defaultFrameColorLabel() const { return m_config.readEntry("defaultFrameColorLabel", 0); } void KisImageConfig::setDefaultFrameColorLabel(int label) { m_config.writeEntry("defaultFrameColorLabel", label); } KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration() { KisProofingConfiguration *proofingConfig= new KisProofingConfiguration(); proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof"); proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA"); proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8"); proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3); if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) { proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } else { proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } QColor def(Qt::green); m_config.readEntry("defaultProofingGamutwarning", def); KoColor col(KoColorSpaceRegistry::instance()->rgb8()); col.fromQColor(def); col.setOpacity(1.0); proofingConfig->warningColor = col; proofingConfig->adaptationState = (double)m_config.readEntry("defaultProofingAdaptationState", 1.0); return toQShared(proofingConfig); } void KisImageConfig::setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState) { m_config.writeEntry("defaultProofingProfileName", proofingSpace->profile()->name()); m_config.writeEntry("defaultProofingProfileModel", proofingSpace->colorModelId().id()); m_config.writeEntry("defaultProofingProfileDepth", proofingSpace->colorDepthId().id()); m_config.writeEntry("defaultProofingProfileIntent", proofingIntent); m_config.writeEntry("defaultProofingBlackpointCompensation", blackPointCompensation); QColor c; c = warningColor.toQColor(); m_config.writeEntry("defaultProofingGamutwarning", c); m_config.writeEntry("defaultProofingAdaptationState",adaptationState); } bool KisImageConfig::useLodForColorizeMask(bool requestDefault) const { return !requestDefault ? m_config.readEntry("useLodForColorizeMask", false) : false; } void KisImageConfig::setUseLodForColorizeMask(bool value) { m_config.writeEntry("useLodForColorizeMask", value); } diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index a6aa2ab531..f4f49e53d2 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1344 +1,1350 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_utils.h" #include #include #include #include "kis_painter.h" #include "kis_image.h" #include "kis_node.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_group_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_meta_data_merge_strategy.h" #include #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "commands/kis_image_layer_move_command.h" #include "commands/kis_image_change_layers_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_abstract_projection_plane.h" #include "kis_processing_applicator.h" #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "commands_new/kis_change_projection_color_command.h" #include "kis_layer_properties_icons.h" #include "lazybrush/kis_colorize_mask.h" #include "commands/kis_node_property_list_command.h" #include namespace KisLayerUtils { void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { KisLayerSP layer = qobject_cast(node.data()); KisSelectionMaskSP mask; if (layer && (mask = layer->selectionMask())) { selectionMasks.append(mask); } } } struct MergeDownInfoBase { MergeDownInfoBase(KisImageSP _image) : image(_image), storage(new SwitchFrameCommand::SharedStorage()) { } virtual ~MergeDownInfoBase() {} KisImageWSP image; QVector selectionMasks; KisNodeSP dstNode; SwitchFrameCommand::SharedStorageSP storage; QSet frames; virtual KisNodeList allSrcNodes() = 0; - virtual KisLayerSP dstLayer() { return 0; } + + KisLayerSP dstLayer() { + return qobject_cast(dstNode.data()); + } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } - - KisLayerSP dstLayer() override { - return qobject_cast(dstNode.data()); - } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); } } KisNodeList mergedNodes; KisNodeList allSrcNodes() override { return mergedNodes; } }; typedef QSharedPointer MergeDownInfoBaseSP; typedef QSharedPointer MergeDownInfoSP; typedef QSharedPointer MergeMultipleInfoSP; struct FillSelectionMasks : public KUndo2Command { FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks); } private: MergeDownInfoBaseSP m_info; }; struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand { DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (dynamic_cast(node.data()) && KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableOnionSkins : public KisCommandUtils::AggregateCommand { DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::onionSkins, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand { DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false) : m_info(info), m_skipIfDstIsGroup(skipIfDstIsGroup) { } void populateChildCommands() override { if (m_skipIfDstIsGroup && m_info->dstLayer() && m_info->dstLayer()->inherits("KisGroupLayer")) { return; } Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::passThrough, false); addCommand(new KisNodePropertyListCommand(node, props)); } } } private: MergeDownInfoBaseSP m_info; bool m_skipIfDstIsGroup; }; struct RefreshHiddenAreas : public KUndo2Command { RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { KisImageAnimationInterface *interface = m_info->image->animationInterface(); const QRect preparedRect = !interface->externalFrameActive() ? m_info->image->bounds() : QRect(); foreach (KisNodeSP node, m_info->allSrcNodes()) { refreshHiddenAreaAsync(node, preparedRect); } } private: QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExactBounds(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); return currentRect; } void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExactBounds(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect); } } } private: MergeDownInfoBaseSP m_info; }; struct RefreshDelayedUpdateLayers : public KUndo2Command { RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { foreach (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); } } private: MergeDownInfoBaseSP m_info; }; struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand { KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing) : m_singleInfo(info), m_finalizing(finalizing) {} KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing) : m_multipleInfo(info), m_finalizing(finalizing), m_putAfter(putAfter) {} void populateChildCommands() override { KisNodeSP prevNode; KisNodeSP nextNode; KisNodeList prevSelection; KisNodeList nextSelection; KisImageSP image; if (m_singleInfo) { prevNode = m_singleInfo->currLayer; nextNode = m_singleInfo->dstNode; image = m_singleInfo->image; } else if (m_multipleInfo) { prevNode = m_putAfter; nextNode = m_multipleInfo->dstNode; prevSelection = m_multipleInfo->allSrcNodes(); image = m_multipleInfo->image; } if (!m_finalizing) { addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(), prevNode, KisNodeSP(), image, false)); } else { addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection, KisNodeSP(), nextNode, image, true)); } } private: MergeDownInfoSP m_singleInfo; MergeMultipleInfoSP m_multipleInfo; bool m_finalizing; KisNodeSP m_putAfter; }; struct CreateMergedLayer : public KisCommandUtils::AggregateCommand { CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized decendant) m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } } private: MergeDownInfoSP m_info; }; struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand { - CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) + CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) : m_info(info), m_name(name) {} void populateChildCommands() override { QString mergedLayerName; - + if (m_name.isEmpty()){ const QString mergedLayerSuffix = i18n("Merged"); mergedLayerName = m_info->mergedNodes.first()->name(); if (!mergedLayerName.endsWith(mergedLayerSuffix)) { mergedLayerName = QString("%1 %2") .arg(mergedLayerName).arg(mergedLayerSuffix); } } else { mergedLayerName = m_name; } - + m_info->dstNode = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } QString compositeOpId; QBitArray channelFlags; bool compositionVaries = false; foreach (KisNodeSP node, m_info->allSrcNodes()) { if (compositeOpId.isEmpty()) { compositeOpId = node->compositeOpId(); } else if (compositeOpId != node->compositeOpId()) { compositionVaries = true; break; } KisLayerSP layer = qobject_cast(node.data()); if (layer && layer->layerStyle()) { compositionVaries = true; break; } } if (!compositionVaries) { if (!compositeOpId.isEmpty()) { m_info->dstNode->setCompositeOpId(compositeOpId); } if (m_info->dstLayer() && !channelFlags.isEmpty()) { m_info->dstLayer()->setChannelFlags(channelFlags); } } } private: MergeMultipleInfoSP m_info; QString m_name; }; struct MergeLayers : public KisCommandUtils::AggregateCommand { MergeLayers(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized decendant) m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer); } private: MergeDownInfoSP m_info; }; struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand { MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { KisPainter gc(m_info->dstNode->paintDevice()); foreach (KisNodeSP node, m_info->allSrcNodes()) { QRect rc = node->exactBounds() | m_info->image->bounds(); node->projectionPlane()->apply(&gc, rc); } } private: MergeMultipleInfoSP m_info; }; struct MergeMetaData : public KUndo2Command { MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy) : m_info(info), m_strategy(strategy) {} void redo() override { QRect layerProjectionExtent = m_info->currLayer->projection()->extent(); QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent(); int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); QList scores; double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); QList srcs; srcs.append(m_info->prevLayer->metaData()); srcs.append(m_info->currLayer->metaData()); m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores); } private: MergeDownInfoSP m_info; const KisMetaData::MergeStrategy *m_strategy; }; KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_selectedBefore(selectedBefore), m_selectedAfter(selectedAfter), m_activeBefore(activeBefore), m_activeAfter(activeAfter), m_image(image) { } void KeepNodesSelectedCommand::end() { KisImageSignalType type; if (isFinalizing()) { type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter); } else { type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore); } m_image->signalRouter()->emitNotification(type); } KisLayerSP constructDefaultLayer(KisImageSP image) { return new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); } RemoveNodeHelper::~RemoveNodeHelper() { } /** * The removal of two nodes in one go may be a bit tricky, because one * of them may be the clone of another. If we remove the source of a * clone layer, it will reincarnate into a paint layer. In this case * the pointer to the second layer will be lost. * * That's why we need to care about the order of the nodes removal: * the clone --- first, the source --- last. */ void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) { const bool lastLayer = scanForLastLayer(image, nodes); while (!nodes.isEmpty()) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; addCommandImpl(new KisImageLayerRemoveCommand(image, node, false, true)); it = nodes.erase(it); } else { ++it; } } } if (lastLayer) { KisLayerSP newLayer = constructDefaultLayer(image); addCommandImpl(new KisImageLayerAddCommand(image, newLayer, image->root(), KisNodeSP(), false, false)); } } bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) { foreach (KisNodeSP node, nodes) { if (node == src) continue; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone && KisNodeSP(clone->copyFrom()) == src) { return true; } } return false; } bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) { bool removeLayers = false; Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) { if (qobject_cast(nodeToRemove.data())) { removeLayers = true; break; } } if (!removeLayers) return false; bool lastLayer = true; KisNodeSP node = image->root()->firstChild(); while (node) { if (!nodesToRemove.contains(node) && qobject_cast(node.data())) { lastLayer = false; break; } node = node->nextSibling(); } return lastLayer; } SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image) : m_nodes(nodes), m_image(image) { } void SimpleRemoveLayers::populateChildCommands() { if (m_nodes.isEmpty()) return; safeRemoveMultipleNodes(m_nodes, m_image); } void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } struct InsertNode : public KisCommandUtils::AggregateCommand { InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} - + void populateChildCommands() override { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, m_putAfter->parent(), m_putAfter, true, false)); - + } private: virtual void addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand { CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) { if (!putAfter) { putAfter = nodesToDelete.last(); } // Add the new merged node on top of the active node -- checking // whether the parent is going to be deleted parent = putAfter->parent(); while (parent && nodesToDelete.contains(parent)) { parent = parent->parent(); } } void populateChildCommands() override { KisNodeList nodesToDelete = m_info->allSrcNodes(); KisNodeSP parent; findPerfectParent(nodesToDelete, m_putAfter, parent); if (!parent) { KisNodeSP oldRoot = m_info->image->root(); KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8)); addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, newRoot, KisNodeSP(), true, false)); addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot)); } else { if (parent == m_putAfter->parent()) { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, m_putAfter, true, false)); } else { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, parent->lastChild(), true, false)); } - reparentSelectionMasks(m_info->image, - m_info->dstLayer(), - m_info->selectionMasks); + /** + * We can merge selection masks, in this case dstLayer is not defined! + */ + if (m_info->dstLayer()) { + reparentSelectionMasks(m_info->image, + m_info->dstLayer(), + m_info->selectionMasks); + } safeRemoveMultipleNodes(m_info->allSrcNodes(), m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { + KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer); + foreach (KisSelectionMaskSP mask, selectionMasks) { addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; SwitchFrameCommand::SharedStorage::~SharedStorage() { } SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage) : FlipFlopCommand(finalize), m_image(image), m_newTime(time), m_storage(storage) {} SwitchFrameCommand::~SwitchFrameCommand() {} void SwitchFrameCommand::init() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_newTime) { m_storage->value = m_newTime; return; } interface->image()->disableUIUpdates(); interface->saveAndResetCurrentTime(m_newTime, &m_storage->value); } void SwitchFrameCommand::end() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_storage->value) { return; } interface->restoreCurrentTime(&m_storage->value); interface->image()->enableUIUpdates(); } struct AddNewFrame : public KisCommandUtils::AggregateCommand { AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {} void populateChildCommands() override { KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper(); KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); channel->addKeyframe(m_frame, cmd); addCommand(cmd); } private: MergeDownInfoBaseSP m_info; int m_frame; }; QSet fetchLayerFrames(KisNodeSP node) { KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) return QSet(); return channel->allKeyframeIds(); } QSet fetchLayerFramesRecursive(KisNodeSP rootNode) { QSet frames = fetchLayerFrames(rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { frames |= fetchLayerFramesRecursive(node); node = node->nextSibling(); } return frames; } void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) { QSet frames = fetchLayerFrames(node); if (frames.isEmpty()) { (*jobs)[0].insert(node); } else { foreach (int frame, frames) { (*jobs)[frame].insert(node); } } } void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) { updateFrameJobs(jobs, rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { updateFrameJobsRecursive(jobs, node); node = node->nextSibling(); } } void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (!layer->visible() && !prevLayer->visible()) { return; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Down")); if (layer->visible() && prevLayer->visible()) { MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER); // in two-layer mode we disable pass trhough only when the destination layer // is not a group layer applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); } applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); applicator.applyCommand(new CleanUpNodes(info, layer), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepMergedNodesSelected(info, true)); } else if (layer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << prevLayer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), layer, image, true)); } else if (prevLayer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << layer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), prevLayer, image, true)); } applicator.end(); } bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents) { KisNodeList nodeParents; KisNodeSP parent = node->parent(); while (parent) { nodeParents << parent; parent = parent->parent(); } foreach(KisNodeSP perspectiveParent, parents) { if (nodeParents.contains(perspectiveParent)) { return true; } } return false; } bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes) { bool result = false; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone) { KisNodeSP cloneSource = KisNodeSP(clone->copyFrom()); Q_FOREACH(KisNodeSP subtree, nodes) { result = recursiveFindNode(subtree, [cloneSource](KisNodeSP node) -> bool { return node == cloneSource; }); if (!result) { result = checkIsCloneOf(cloneSource, nodes); } if (result) { break; } } } return result; } void filterMergableNodes(KisNodeList &nodes, bool allowMasks) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((!allowMasks && !qobject_cast(it->data())) || checkIsChildOf(*it, nodes)) { qDebug() << "Skipping node" << ppVar((*it)->name()); it = nodes.erase(it); } else { ++it; } } } void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes) { KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root); if (it != inputNodes.end()) { outputNodes << *it; inputNodes.erase(it); } if (inputNodes.isEmpty()) { return; } KisNodeSP child = root->firstChild(); while (child) { sortMergableNodes(child, inputNodes, outputNodes); child = child->nextSibling(); } /** * By the end of recursion \p inputNodes must be empty */ KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty()); } KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes) { KisNodeList result; sortMergableNodes(root, nodes, result); return result; } KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks) { KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; } KisNodeSP root; Q_FOREACH(KisNodeSP node, nodes) { KisNodeSP localRoot = node; while (localRoot->parent()) { localRoot = localRoot->parent(); } if (!root) { root = localRoot; } KIS_ASSERT_RECOVER(root == localRoot) { return nodes; } } KisNodeList result; sortMergableNodes(root, nodes, result); filterMergableNodes(result, allowMasks); return result; } void addCopyOfNameTag(KisNodeSP node) { const QString prefix = i18n("Copy of"); QString newName = node->name(); if (!newName.startsWith(prefix)) { newName = QString("%1 %2").arg(prefix).arg(newName); node->setName(newName); } } KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot) { KisNodeList nodes; if ((!excludeRoot || root->parent()) && root->check(props)) { nodes << root; } KisNodeSP node = root->firstChild(); while (node) { nodes += findNodesWithProps(node, props, excludeRoot); node = node->nextSibling(); } return nodes; } KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter) { KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; } KIS_ASSERT_RECOVER(putAfter) { return nodes; } KisNodeList visibleNodes; int putAfterIndex = -1; Q_FOREACH(KisNodeSP node, nodes) { if (node->visible()) { visibleNodes << node; } else { *invisibleNodes << node; if (node == *putAfter) { putAfterIndex = visibleNodes.size() - 1; } } } if (!visibleNodes.isEmpty() && putAfterIndex >= 0) { putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1); *putAfter = visibleNodes[putAfterIndex]; } return visibleNodes; } void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE, emitSignals, kundo2_i18n("Change projection color"), 0, 142857 + 1); applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); applicator.end(); } - void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, - bool flattenSingleLayer, const KUndo2MagicString &actionName, + void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, + bool flattenSingleLayer, const KUndo2MagicString &actionName, bool cleanupNodes = true, const QString layerName = QString()) { if (!putAfter) { putAfter = mergedNodes.first(); } filterMergableNodes(mergedNodes); { KisNodeList tempNodes; qSwap(mergedNodes, tempNodes); sortMergableNodes(image->root(), tempNodes, mergedNodes); } if (mergedNodes.size() <= 1 && (!flattenSingleLayer && mergedNodes.size() == 1)) return; KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, actionName); KisNodeList originalNodes = mergedNodes; KisNodeList invisibleNodes; mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter); if (!invisibleNodes.isEmpty()) { applicator.applyCommand( new SimpleRemoveLayers(invisibleNodes, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) { MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new DisablePassThroughForHeadsOnly(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); } //applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); if (cleanupNodes){ applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } else { applicator.applyCommand(new InsertNode(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true)); } applicator.end(); } void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes")); } - + void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible")); } struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand { MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter){} void populateChildCommands() override { KisNodeSP parent; CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent); KisLayerSP parentLayer; do { parentLayer = qobject_cast(parent.data()); parent = parent->parent(); } while(!parentLayer && parent); KisSelectionSP selection = new KisSelection(); foreach (KisNodeSP node, m_info->allSrcNodes()) { KisMaskSP mask = dynamic_cast(node.data()); if (!mask) continue; selection->pixelSelection()->applySelection( mask->selection()->pixelSelection(), SELECTION_ADD); } KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image); mergedMask->initSelection(parentLayer); mergedMask->setSelection(selection); m_info->dstNode = mergedMask; } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand { ActivateSelectionMask(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { KisSelectionMaskSP mergedMask = dynamic_cast(m_info->dstNode.data()); addCommand(new KisActivateSelectionMaskCommand(mergedMask, true)); } private: MergeDownInfoBaseSP m_info; }; bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { QList selectionMasks; for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) { KisSelectionMaskSP mask = dynamic_cast(it->data()); if (!mask) { it = mergedNodes.erase(it); } else { selectionMasks.append(mask); ++it; } } if (mergedNodes.isEmpty()) return false; KisLayerSP parentLayer = qobject_cast(selectionMasks.first()->parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return 0; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Selection Masks")); MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); applicator.applyCommand(new MergeSelectionMasks(info, putAfter)); applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new ActivateSelectionMask(info)); applicator.end(); return true; } void flattenLayer(KisImageSP image, KisLayerSP layer) { if (!layer->childCount() && !layer->layerStyle()) return; KisNodeList mergedNodes; mergedNodes << layer; mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer")); } void flattenImage(KisImageSP image) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, 0, true, kundo2_i18n("Flatten Image")); } KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_nodes(nodes) { } void KisSimpleUpdateCommand::end() { updateNodes(m_nodes); } void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes) { Q_FOREACH(KisNodeSP node, nodes) { node->setDirty(node->extent()); } } void recursiveApplyNodes(KisNodeSP node, std::function func) { func(node); node = node->firstChild(); while (node) { recursiveApplyNodes(node, func); node = node->nextSibling(); } } KisNodeSP recursiveFindNode(KisNodeSP node, std::function func) { if (func(node)) { return node; } node = node->firstChild(); while (node) { KisNodeSP resultNode = recursiveFindNode(node, func); if (resultNode) { return resultNode; } node = node->nextSibling(); } return 0; } KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid) { return recursiveFindNode(root, [uuid] (KisNodeSP node) { return node->uuid() == uuid; }); } } diff --git a/libs/image/kis_paint_layer.h b/libs/image/kis_paint_layer.h index 42b074f6f7..472af11f57 100644 --- a/libs/image/kis_paint_layer.h +++ b/libs/image/kis_paint_layer.h @@ -1,177 +1,177 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINT_LAYER_H_ #define KIS_PAINT_LAYER_H_ #include "kis_types.h" #include "kis_layer.h" #include "kis_indirect_painting_support.h" #include class KoColorSpace; /** - * This layer is of a type that can be d on. A paint layer can + * This layer is of a type that can be drawn on. A paint layer can * have any number of effect masks, a transparency mask, a local * selection and a protection mask. * * The protection mask can be read/write, read-only or write-only. * The transparency mask has two rendering forms: as a selection mask * and by changing the transparency of the paint layer's pixels. */ class KRITAIMAGE_EXPORT KisPaintLayer : public KisLayer, public KisIndirectPaintingSupport { Q_OBJECT public: /** * Construct a paint layer with the given parameters. The default bounds of the paintdevice are overwriten. * @param image this layer belongs to, or null, if it shouldn't belong to any image * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 * @param dev is the paint device, that should be used */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, KisPaintDeviceSP dev); /** * Construct a paint layer with the given parameters * @param image this layer belongs to. it must not be null and it must have a valid color space. * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity); /** * Construct a paint layer with the given parameters * @param image this layer belongs to, or null, if it shouldn't belong to any image. image must not be null, if colorSpace is null * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 * @param colorSpace is the color space, that should be used to construct the paint device. it can be null, if the image is valid. */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, const KoColorSpace * colorSpace); /** * Copy Constructor */ KisPaintLayer(const KisPaintLayer& rhs); ~KisPaintLayer() override; KisNodeSP clone() const override { return KisNodeSP(new KisPaintLayer(*this)); } bool allowAsChild(KisNodeSP) const override; const KoColorSpace * colorSpace() const override; bool needProjection() const override; QIcon icon() const override; void setImage(KisImageWSP image) override; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; public: QRect extent() const override; QRect exactBounds() const override; bool accept(KisNodeVisitor &v) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; /** * set the channelflags for locking certain channels (used by painting tools) * for this layer to the specified bit array. * The bit array must have exactly the same number of channels as * the colorspace this layer is in, or be empty, in which case all * channels are active. */ void setChannelLockFlags(const QBitArray& channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is locked or not (used by painting tools). * If the channelflags bit array is empty, all channels are active. */ const QBitArray& channelLockFlags() const; /** * Returns the paintDevice that accompanies this layer */ KisPaintDeviceSP paintDevice() const override; /** * Returns the original pixels before masks have been applied. */ KisPaintDeviceSP original() const override; /** * @returns true when painting should not affect the alpha channel */ bool alphaLocked() const; /** * @param l if true, the alpha channel will be protected from modification */ void setAlphaLocked(bool lock); /** * @return true if onion skins should be rendered on this layer */ bool onionSkinEnabled() const; /** * @param state whether onion skins should be rendered */ void setOnionSkinEnabled(bool state); KisPaintDeviceList getLodCapableDevices() const override; public Q_SLOTS: void slotExternalUpdateOnionSkins(); public: // KisIndirectPaintingSupport KisLayer* layer() { return this; } protected: // override from KisLayer void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const override; KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; private: void init(KisPaintDeviceSP paintDevice, const QBitArray &paintChannelFlags = QBitArray()); struct Private; Private * const m_d; }; typedef KisSharedPtr KisPaintLayerSP; #endif // KIS_PAINT_LAYER_H_ diff --git a/libs/image/kis_time_range.cpp b/libs/image/kis_time_range.cpp index 9f44a16117..85c918e961 100644 --- a/libs/image/kis_time_range.cpp +++ b/libs/image/kis_time_range.cpp @@ -1,103 +1,100 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_time_range.h" #include #include "kis_keyframe_channel.h" #include "kis_node.h" struct KisTimeRangeStaticRegistrar { KisTimeRangeStaticRegistrar() { qRegisterMetaType("KisTimeRange"); } }; static KisTimeRangeStaticRegistrar __registrar; QDebug operator<<(QDebug dbg, const KisTimeRange &r) { dbg.nospace() << "KisTimeRange(" << r.start() << ", " << r.end() << ")"; return dbg.space(); } void KisTimeRange::calculateTimeRangeRecursive(const KisNode *node, int time, KisTimeRange &range, bool exclusive) { if (!node->visible()) return; - - const QList channels = node->keyframeChannels(); - - Q_FOREACH (const KisKeyframeChannel *channel, channels) { + Q_FOREACH (const KisKeyframeChannel *channel, node->keyframeChannels()) { if (exclusive) { // Intersection range &= channel->identicalFrames(time); } else { // Union range |= channel->affectedFrames(time); } } KisNodeSP child = node->firstChild(); while (child) { calculateTimeRangeRecursive(child, time, range, exclusive); child = child->nextSibling(); } } namespace KisDomUtils { void saveValue(QDomElement *parent, const QString &tag, const KisTimeRange &range) { QDomDocument doc = parent->ownerDocument(); QDomElement e = doc.createElement(tag); parent->appendChild(e); e.setAttribute("type", "timerange"); if (range.isValid()) { e.setAttribute("from", toString(range.start())); if (!range.isInfinite()) { e.setAttribute("to", toString(range.end())); } } } bool loadValue(const QDomElement &parent, const QString &tag, KisTimeRange *range) { QDomElement e; if (!findOnlyElement(parent, tag, &e)) return false; if (!Private::checkType(e, "timerange")) return false; int start = toInt(e.attribute("from", "-1")); int end = toInt(e.attribute("to", "-1")); if (start == -1) { range = new KisTimeRange(); } else if (end == -1) { *range = KisTimeRange::infinite(start); } else { *range = KisTimeRange::fromTime(start, end); } return true; } } diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index 5525ece21e..f7e5f97041 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,514 +1,549 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This 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 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 "Document.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 struct Document::Private { Private() {} QPointer document; }; Document::Document(KisDocument *document, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; } Document::~Document() { delete d; } bool Document::operator==(const Document &other) const { return (d->document == other.d->document); } bool Document::operator!=(const Document &other) const { return !(operator==(other)); } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { return new Node(d->document->image(), activeNodes.first()); } return new Node(d->document->image(), d->document->image()->root()->firstChild()); } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; if (viewManager->document() != d->document) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QList Document::topLevelNodes() const { if (!d->document) return QList(); Node n(d->document->image(), d->document->image()->rootLayer()); return n.childNodes(); } Node *Document::nodeByName(const QString &name) const { if (!d->document) return 0; KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name); return new Node(d->document->image(), node); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; bool retval = d->document->image()->assignImageProfile(profile); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { - KoXmlDocument doc = KoXmlDocument(true); + KoXmlDocument doc; QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString::null; return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->width(), value); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return new Node(image, image->root()); } Selection *Document::selection() const { if (!d->document) return 0; if (!d->document->image()) return 0; if (!d->document->image()->globalSelection()) return 0; return new Selection(d->document->image()->globalSelection()); } void Document::setSelection(Selection* value) { if (!d->document) return; if (!d->document->image()) return; if (value) { d->document->image()->setGlobalSelection(value->selection()); } else { d->document->image()->setGlobalSelection(0); } } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(value, d->document->image()->height()); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes(); } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes(); } void Document::setYRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes); } QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } bool Document::close() { bool retval = d->document->closeUrl(false); Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == d->document) { view->close(); view->deleteLater(); } } d->document->deleteLater(); d->document = 0; return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; + QString mimeType = KisMimeDatabase::mimeTypeForFile(filename); + d->document->setOutputMimeType(mimeType.toLatin1()); return d->document->exportDocument(QUrl::fromLocalFile(filename), exportConfiguration.configuration()); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->flatten(); } void Document::resizeImage(int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } +void Document::scaleImage(int w, int h, int xres, int yres, QString strategy) +{ + if (!d->document) return; + KisImageSP image = d->document->image(); + if (!image) return; + QRect rc = image->bounds(); + rc.setWidth(w); + rc.setHeight(h); + + KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); + if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); + + image->scaleImage(rc.size(), xres, yres, actualStrategy); +} + +void Document::rotateImage(double radians) +{ + if (!d->document) return; + KisImageSP image = d->document->image(); + if (!image) return; + image->rotateImage(radians); +} + +void Document::shearImage(double angleX, double angleY) +{ + if (!d->document) return; + KisImageSP image = d->document->image(); + if (!image) return; + image->shear(angleX, angleY); +} + bool Document::save() { if (!d->document) return false; return d->document->save(); } bool Document::saveAs(const QString &filename) { if (!d->document) return false; return d->document->saveAs(QUrl::fromLocalFile(filename)); } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType == "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType == "selectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } QImage Document::projection(int x, int y, int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->image()->convertToQImage(x, y, w, h, 0); } QImage Document::thumbnail(int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->generatePreview(QSize(w, h)).toImage(); } void Document::lock() { if (!d->document || !d->document->image()) return; d->document->image()->barrierLock(); } void Document::unlock() { if (!d->document || !d->document->image()) return; d->document->image()->unlock(); } void Document::waitForDone() { if (!d->document || !d->document->image()) return; d->document->image()->waitForDone(); } bool Document::tryBarrierLock() { if (!d->document || !d->document->image()) return false; return d->document->image()->tryBarrierLock(); } bool Document::isIdle() { if (!d->document || !d->document->image()) return false; return d->document->image()->isIdle(); } void Document::refreshProjection() { if (!d->document || !d->document->image()) return; d->document->image()->refreshGraph(); } QPointer Document::document() const { return d->document; } diff --git a/libs/libkis/Document.h b/libs/libkis/Document.h index 54d365df8d..e17a52db01 100644 --- a/libs/libkis/Document.h +++ b/libs/libkis/Document.h @@ -1,478 +1,514 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This 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 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 LIBKIS_DOCUMENT_H #define LIBKIS_DOCUMENT_H #include #include "kritalibkis_export.h" #include "libkis.h" class KisDocument; /** * The Document class encapsulates a Krita Document/Image. A Krita document is an Image with * a filename. Libkis does not differentiate between a document and an image, like Krita does * internally. */ class KRITALIBKIS_EXPORT Document : public QObject { Q_OBJECT Q_DISABLE_COPY(Document) public: explicit Document(KisDocument *document, QObject *parent = 0); ~Document() override; bool operator==(const Document &other) const; bool operator!=(const Document &other) const; public Q_SLOTS: /** * Batchmode means that no actions on the document should show dialogs or popups. * @return true if the document is in batchmode. */ bool batchmode() const; /** * Set batchmode to @param value. If batchmode is true, then there should be no popups * or dialogs shown to the user. */ void setBatchmode(bool value); /** * @brief activeNode retrieve the node that is currently active in the currently active window * @return the active node. If there is no active window, the first child node is returned. */ Node* activeNode() const; /** * @brief setActiveNode make the given node active in the currently active view and window * @param value the node to make active. */ void setActiveNode(Node* value); /** * @brief toplevelNodes return a list with all top level nodes in the image graph */ QList topLevelNodes() const; /** * @brief nodeByName searches the node tree for a node with the given name and returns it * @param name the name of the node * @return the first node with the given name or 0 if no node is found */ Node *nodeByName(const QString &name) const; /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return false if the colorProfile name does not correspond to to a registered profile or if assigning * the profile failed. */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the nodes and the image to the given colorspace. The conversion is * done with Perceptual as intent, High Quality and No LCMS Optimizations as flags and no blackpoint * compensation. * * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. * @return false the combination of these arguments does not correspond to a colorspace. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief documentInfo creates and XML document representing document and author information. * @return a string containing a valid XML document with the right information about the document * and author. The DTD can be found here: * * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/ * * @code * * * * * My Document * * * * * Unknown * 1 * 35 * 2017-02-27T20:15:09 * 2017-02-27T20:14:33 * * * * Boudewijn Rempt * * * * * * * * * * * * * * * @endcode * */ QString documentInfo() const; /** * @brief setDocumentInfo set the Document information to the information contained in document * @param document A string containing a valid XML document that conforms to the document-info DTD * that can be found here: * * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/ */ void setDocumentInfo(const QString &document); /** * @return the full path to the document, if it has been set. */ QString fileName() const; /** * @brief setFileName set the full path of the document to @param value */ void setFileName(QString value); /** * @return the height of the image in pixels */ int height() const; /** * @brief setHeight resize the document to @param value height. This is a canvas resize, not a scale. */ void setHeight(int value); /** * @return the name of the document. This is the title field in the @see documentInfo */ QString name() const; /** * @brief setName sets the name of the document to @param value. This is the title field in the @see documentInfo */ void setName(QString value); /** * @return the resolution in pixels per inch */ int resolution() const; /** * @brief setResolution set the resolution of the image; this does not scale the image * @param value the resolution in pixels per inch */ void setResolution(int value); /** * @brief rootNode the root node is the invisible group layer that contains the entire node * hierarchy. * @return the root of the image */ Node* rootNode() const; /** * @brief selection Create a Selection object around the global selection, if there is one. * @return the global selection or None if there is no global selection. */ Selection* selection() const; /** * @brief setSelection set or replace the global selection * @param value a valid selection object. */ void setSelection(Selection* value); /** * @return the width of the image in pixels. */ int width() const; /** * @brief setWidth resize the document to @param value width. This is a canvas resize, not a scale. */ void setWidth(int value); /** * @return xRes the horizontal resolution of the image in pixels per pt (there are 72 pts to an inch) */ double xRes() const; /** * @brief setXRes set the horizontal resolution of the image to xRes in pixels per pt. (there are 72 pts to an inch) */ void setXRes(double xRes) const; /** * @return yRes the vertical resolution of the image in pixels per pt (there are 72 pts to an inch) */ double yRes() const; /** * @brief setYRes set the vertical resolution of the image to yRes in pixels per pt. (there are 72 pts to an inch) */ void setYRes(double yRes) const; /** * @brief pixelData reads the given rectangle from the image projection and returns it as a byte * array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the image boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original image data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; /** * @brief close Close the document: remove it from Krita's internal list of documents and * close all views. If the document is modified, you should save it first. There will be * no prompt for saving. * @return true if the document is closed. */ bool close(); /** * @brief crop the image to rectangle described by @param x, @param y, * @param w and @param h */ void crop(int x, int y, int w, int h); /** * @brief exportImage export the image, without changing its URL to the given path. * @param filename the full path to which the image is to be saved * @param exportConfiguration a configuration object appropriate to the file format * @return true if the export succeeded, false if it failed. */ bool exportImage(const QString &filename, const InfoObject &exportConfiguration); /** * @brief flatten all layers in the image */ void flatten(); /** - * @brief resizeImage resize the image to the given width and height. + * @brief resizeImage resizes the canvas to the given width and height. + * Note: This doesn't scale, use scale image for that. * @param w the new width * @param h the new height */ void resizeImage(int w, int h); + /** + * @brief scaleImage + * @param w the new width + * @param h the new height + * @param xres the new xres + * @param yres the new yres + * @param strategy the scaling strategy. There's several ones amongst these that aren't available in the regular UI. + * The list of filters is extensible and can be retrieved with Krita::filter + *
    + *
  • Hermite
  • + *
  • Bicubic - Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.
  • + *
  • Box - Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.
  • + *
  • Bilinear - Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.
  • + *
  • Bell
  • + *
  • BSpline
  • + *
  • Kanczos3 - Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.
  • + *
  • Mitchell
  • + *
+ */ + void scaleImage(int w, int h, int xres, int yres, QString strategy); + + /** + * @brief rotateImage + * Rotate the image by the given radians. + * @param radians the amount you wish to rotate the image in radians + */ + void rotateImage(double radians); + + /** + * @brief shearImage shear the whole image. + * @param angleX the X-angle in degrees to shear by + * @param angleY the Y-angle in degrees to shear by + */ + void shearImage(double angleX, double angleY); + /** * @brief save the image to its currently set path. The modified flag of the * document will be reset * @return true if saving succeeded, false otherwise. */ bool save(); /** * @brief saveAs save the document under the @param filename. The document's * filename will be reset to @param filename. * @param filename the new filename (full path) for the document * @return true if saving succeeded, false otherwise. */ bool saveAs(const QString &filename); /** * @brief createNode create a new node of the given type. The node is not added * to the node hierarchy; you need to do that by finding the right parent node, * getting its list of child nodes and adding the node in the right place, then * calling Node::SetChildNodes * * @param name The name of the node * * @param nodeType The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
* * When relevant, the new Node will have the colorspace of the image by default; * that can be changed with Node::setColorSpace. * * The settings and selections for relevant layer and mask types can also be set * after the Node has been created. * * @return the new Node. */ Node* createNode(const QString &name, const QString &nodeType); /** * @brief projection creates a QImage from the rendered image or * a cutout rectangle. */ QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const; /** * @brief thumbnail create a thumbnail of the given dimensions. * * If the requested size is too big a null QImage is created. * * @return a QImage representing the layer contents. */ QImage thumbnail(int w, int h) const; /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void lock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void unlock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void waitForDone(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ bool tryBarrierLock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ bool isIdle(); /** * Starts a synchronous recomposition of the projection: everything will * wait until the image is fully recomputed. */ void refreshProjection(); private: friend class Krita; friend class Window; friend class Filter; QPointer document() const; private: struct Private; Private *const d; }; #endif // LIBKIS_DOCUMENT_H diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp index 0275aeb97e..9012f5f1a7 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,377 +1,383 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This 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 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 "Krita.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 "View.h" #include "Document.h" #include "Window.h" #include "Extension.h" #include "DockWidgetFactoryBase.h" #include "Filter.h" #include "InfoObject.h" #include "Resource.h" Krita* Krita::s_instance = 0; struct Krita::Private { Private() {} QList extensions; bool batchMode {false}; Notifier *notifier{new Notifier()}; }; Krita::Krita(QObject *parent) : QObject(parent) , d(new Private) { qRegisterMetaType(); } Krita::~Krita() { qDeleteAll(d->extensions); delete d->notifier; delete d; } QList Krita::actions() const { QList actionList; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return actionList; } KActionCollection *actionCollection = mainWindow->actionCollection(); Q_FOREACH(QAction *action, actionCollection->actions()) { actionList << new Action(action->objectName(), action); } return actionList; } Action *Krita::action(const QString &name) const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KActionCollection *actionCollection = mainWindow->actionCollection(); QAction *action = actionCollection->action(name); if (action) { return new Action(name, action); } return 0; } Document* Krita::activeDocument() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KisView *view = mainWindow->activeView(); if (!view) { return 0; } KisDocument *document = view->document(); return new Document(document); } void Krita::setActiveDocument(Document* value) { Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == value->document().data()) { view->activateWindow(); break; } } } bool Krita::batchmode() const { return d->batchMode; } void Krita::setBatchmode(bool value) { d->batchMode = value; } QList Krita::documents() const { QList ret; foreach(QPointer doc, KisPart::instance()->documents()) { ret << new Document(doc); } return ret; } QStringList Krita::filters() const { QStringList ls = KisFilterRegistry::instance()->keys(); qSort(ls); return ls; } Filter *Krita::filter(const QString &name) const { if (!filters().contains(name)) return 0; Filter *filter = new Filter(); filter->setName(name); KisFilterSP f = KisFilterRegistry::instance()->value(name); KisFilterConfigurationSP fc = f->defaultConfiguration(); InfoObject *info = new InfoObject(fc); filter->setConfiguration(info); return filter; } +QStringList Krita::filterStrategies() const +{ + return KisFilterStrategyRegistry::instance()->keys(); +} + QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const { QSet profileNames; QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth); QList profiles = KoColorSpaceRegistry::instance()->profilesFor(id); Q_FOREACH(const KoColorProfile *profile, profiles) { profileNames << profile->name(); } return profileNames.toList(); } bool Krita::addProfile(const QString &profilePath) { KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); return iccEngine->addProfile(profilePath); } Notifier* Krita::notifier() const { return d->notifier; } QString Krita::version() const { return KritaVersionWrapper::versionString(true); } QList Krita::views() const { QList ret; foreach(QPointer view, KisPart::instance()->views()) { ret << new View(view); } return ret; } Window *Krita::activeWindow() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } return new Window(mainWindow); } QList Krita::windows() const { QList ret; foreach(QPointer mainWin, KisPart::instance()->mainWindows()) { ret << new Window(mainWin); } return ret; } QMap Krita::resources(const QString &type) const { QMap resources = QMap (); if (type == "pattern") { KoResourceServer* server = KoResourceServerProvider::instance()->patternServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "gradient") { KoResourceServer* server = KoResourceServerProvider::instance()->gradientServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "brush") { KisBrushResourceServer* server = KisBrushServer::instance()->brushServer(); Q_FOREACH (KisBrushSP res, server->resources()) { resources[res->name()] = new Resource(res.data()); } } else if (type == "preset") { KisPaintOpPresetResourceServer* server = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (KisPaintOpPresetSP res, server->resources()) { resources[res->name()] = new Resource(res.data()); } } else if (type == "palette") { KoResourceServer* server = KoResourceServerProvider::instance()->paletteServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "workspace") { KoResourceServer< KisWorkspaceResource >* server = KisResourceServerProvider::instance()->workspaceServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } return resources; } Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); Q_ASSERT(cs); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); if (!document->newImage(name, width, height, cs, bgColor, true, 1, "", 100.0)) { qDebug() << "Could not create a new image"; return 0; } Q_ASSERT(document->image()); qDebug() << document->image()->objectName(); return new Document(document); } Document* Krita::openDocument(const QString &filename) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); document->openUrl(QUrl::fromLocalFile(filename), KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES); return new Document(document); } Window* Krita::openWindow() { KisMainWindow *mw = KisPart::instance()->createMainWindow(); return new Window(mw); } Action *Krita::createAction(const QString &text) { KisAction *action = new KisAction(text, this); KisPart::instance()->addScriptAction(action); return new Action(action->objectName(), action); } void Krita::addExtension(Extension* extension) { d->extensions.append(extension); } QList< Extension* > Krita::extensions() { return d->extensions; } void Krita::writeSetting(const QString &group, const QString &name, const QString &value) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); grp.writeEntry(name, value); } QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); return grp.readEntry(name, defaultValue); } void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory) { KoDockRegistry::instance()->add(factory); } Krita* Krita::instance() { if (!s_instance) { s_instance = new Krita; } return s_instance; } /** * Scripter.fromVariant(variant) * variant is a QVariant * returns instance of QObject-subclass * * This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget */ QObject *Krita::fromVariant(const QVariant& v) { if (v.canConvert< QWidget* >()) { QObject* obj = qvariant_cast< QWidget* >(v); return obj; } else if (v.canConvert< QObject* >()) { QObject* obj = qvariant_cast< QObject* >(v); return obj; } else return 0; } diff --git a/libs/libkis/Krita.h b/libs/libkis/Krita.h index 183e3d08ce..beefe0f47f 100644 --- a/libs/libkis/Krita.h +++ b/libs/libkis/Krita.h @@ -1,293 +1,301 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This 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 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 LIBKIS_KRITA_H #define LIBKIS_KRITA_H #include #include "kritalibkis_export.h" #include "libkis.h" #include "Extension.h" #include "Document.h" #include "Window.h" #include "View.h" #include "Action.h" #include "Notifier.h" class QAction; /** * Krita is a singleton class that offers the root access to the Krita object hierarchy. * * The Krita.instance() is aliased as two builtins: Scripter and Application. */ class KRITALIBKIS_EXPORT Krita : public QObject { Q_OBJECT public: explicit Krita(QObject *parent = 0); ~Krita() override; public Q_SLOTS: /** * @return the currently active document, if there is one. */ Document* activeDocument() const; /** * @brief setActiveDocument activates the first view that shows the given document * @param value the document we want to activate */ void setActiveDocument(Document* value); /** * @brief batchmode determines whether the script is run in batch mode. If batchmode * is true, scripts should now show messageboxes or dialog boxes. * * Note that this separate from Document.setBatchmode(), which determines whether * export/save option dialogs are shown. * * @return true if the script is run in batchmode */ bool batchmode() const; /** * @brief setBatchmode sets the the batchmode to @param value; if true, scripts should * not show dialogs or messageboxes. */ void setBatchmode(bool value); /** * @return return a list of all actions for the currently active mainWindow. */ QList actions() const; /** * @return the action that has been registered under the given name, or 0 if no such action exists. */ Action *action(const QString &name) const; /** * @return a list of all open Documents */ QList documents() const; /** * @brief Filters are identified by an internal name. This function returns a list * of all existing registered filters. * @return a list of all registered filters */ QStringList filters() const; /** * @brief filter construct a Filter object with a default configuration. * @param name the name of the filter. Use Krita.instance().filters() to get * a list of all possible filters. * @return the filter or None if there is no such filter. */ Filter *filter(const QString &name) const; + /** + * @brief filterStrategies Retrieves all installed filter strategies. A filter + * strategy is used when transforming (scaling, shearing, rotating) an image to + * calculate the value of the new pixels. You can use th + * @return the id's of all available filters. + */ + QStringList filterStrategies() const; + /** * @brief profiles creates a list with the names of all color profiles compatible * with the given color model and color depth. * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return a list with valid names */ QStringList profiles(const QString &colorModel, const QString &colorDepth) const; /** * @brief addProfile load the given profile into the profile registry. * @param profilePath the path to the profile. * @return true if adding the profile succeeded. */ bool addProfile(const QString &profilePath); /** * @brief notifier the Notifier singleton emits signals when documents are opened and * closed, the configuration changes, views are opened and closed or windows are opened. * @return the notifier object */ Notifier* notifier() const; /** * @brief version Determine the version of Krita * * Usage: print(Application.version ()) * * @return the version string including git sha1 if Krita was built from git */ QString version() const; /** * @return a list of all views. A Document can be shown in more than one view. */ QList views() const; /** * @return the currently active window or None if there is no window */ Window *activeWindow() const; /** * @return a list of all windows */ QList windows() const; /** * @brief resources returns a list of Resource objects of the given type * @param type Valid types are: * *
    *
  • pattern
  • *
  • gradient
  • *
  • brush
  • *
  • preset
  • *
  • palette
  • *
  • workspace
  • *
*/ QMap resources(const QString &type) const; /** * @brief createDocument creates a new document and image and registers the document with the Krita application. * * The document will have one transparent layer. * * @param width the width in pixels * @param height the height in pixels * @param name the name of the image (not the filename of the document) * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param profile The name of an icc profile that is known to Krita. If an empty string is passed, the default is * taken. * @return the created document. */ Document *createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile); /** * @brief openDocument creates a new Document, registers it with the Krita application and loads the given file. * @param filename the file to open in the document * @return the document */ Document *openDocument(const QString &filename); /** * @brief openWindow create a new main window. The window is not shown by default. */ Window *openWindow(); /** * @brief createAction creates an action with the given text and passes it to Krita. Every newly created * mainwindow will create an instance of this action. * @param text the user-visible text * @return the Action you can connect a slot to. */ Action *createAction(const QString &text); /** * @brief addExtension add the given plugin to Krita. There will be a single instance of each Extension in the Krita process. * @param extension the extension to add. */ void addExtension(Extension* extension); /** * return a list with all registered extension objects. */ QList extensions(); /** * @brief addDockWidgetFactory Add the given docker factory to the application. For scripts * loaded on startup, this means that every window will have one of the dockers created by the * factory. * @param factory The factory object. */ void addDockWidgetFactory(DockWidgetFactoryBase* factory ); /** * @brief writeSetting write the given setting under the given name to the kritarc file in * the given settings group. * @param group The group the setting belongs to. If empty, then the setting is written in the * general section * @param name The name of the setting * @param value The value of the setting. Script settings are always written as strings. */ void writeSetting(const QString &group, const QString &name, const QString &value); /** * @brief readSetting read the given setting value from the kritarc file. * @param group The group the setting is part of. If empty, then the setting is read from * the general group. * @param name The name of the setting * @param defaultValue The default value of the setting * @return a string representing the setting. */ QString readSetting(const QString &group, const QString &name, const QString &defaultValue); /** * @brief instance retrieve the singleton instance of the Application object. */ static Krita* instance(); // Internal only: for use with mikro.py static QObject *fromVariant(const QVariant& v); private: struct Private; Private *const d; static Krita* s_instance; }; Q_DECLARE_METATYPE(Notifier*); #endif // LIBKIS_KRITA_H diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp index e7a716d84d..4aff84e937 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,503 +1,568 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This 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 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 #include +#include + +#include +#include #include "Krita.h" #include "Node.h" #include "Channel.h" #include "Filter.h" #include "Selection.h" struct Node::Private { Private() {} KisImageSP image; KisNodeSP node; }; Node::Node(KisImageSP image, KisNodeSP node, QObject *parent) : QObject(parent) , d(new Private) { d->image = image; d->node = node; } Node::~Node() { delete d; } bool Node::operator==(const Node &other) const { return (d->node == other.d->node && d->image == other.d->image); } bool Node::operator!=(const Node &other) const { return !(operator==(other)); } bool Node::alphaLocked() const { if (!d->node) return false; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { return paintLayer->alphaLocked(); } return false; } void Node::setAlphaLocked(bool value) { if (!d->node) return; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { paintLayer->setAlphaLocked(value); } } QString Node::blendingMode() const { if (!d->node) return QString(); return d->node->compositeOpId(); } void Node::setBlendingMode(QString value) { if (!d->node) return; d->node->setCompositeOpId(value); } QList Node::channels() const { QList channels; if (!d->node) return channels; if (!d->node->inherits("KisLayer")) return channels; Q_FOREACH(KoChannelInfo *info, d->node->colorSpace()->channels()) { Channel *channel = new Channel(d->node, info); channels << channel; } return channels; } QList Node::childNodes() const { QList nodes; if (d->node) { int childCount = d->node->childCount(); for (int i = 0; i < childCount; ++i) { nodes << new Node(d->image, d->node->at(i)); } } return nodes; } bool Node::addChildNode(Node *child, Node *above) { if (!d->node) return false; return d->image->addNode(child->node(), d->node, above->node()); } bool Node::removeChildNode(Node *child) { if (!d->node) return false; return d->image->removeNode(child->node()); } void Node::setChildNodes(QList nodes) { if (!d->node) return; KisNodeSP node = d->node->firstChild(); while (node) { d->image->removeNode(node); node = node->nextSibling(); } Q_FOREACH(Node *node, nodes) { d->image->addNode(node->node(), d->node); } } int Node::colorLabel() const { if (!d->node) return 0; return d->node->colorLabelIndex(); } void Node::setColorLabel(int index) { if (!d->node) return; d->node->setColorLabelIndex(index); } QString Node::colorDepth() const { if (!d->node) return ""; return d->node->colorSpace()->colorDepthId().id(); } QString Node::colorModel() const { if (!d->node) return ""; return d->node->colorSpace()->colorModelId().id(); } QString Node::colorProfile() const { if (!d->node) return ""; return d->node->colorSpace()->profile()->name(); } bool Node::setColorProfile(const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(srcCS->colorModelId().id(), srcCS->colorDepthId().id(), profile); KisChangeProfileVisitor v(srcCS, dstCs); return layer->accept(v); } bool Node::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); KisColorSpaceConvertVisitor v(d->image, srcCS, dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return layer->accept(v); } bool Node::animated() const { if (!d->node) return false; return d->node->isAnimated(); } void Node::enableAnimation() const { if (!d->node) return; d->node->enableAnimation(); } bool Node::collapsed() const { if (!d->node) return false; return d->node->collapsed(); } void Node::setCollapsed(bool collapsed) { if (!d->node) return; d->node->setCollapsed(collapsed); } bool Node::inheritAlpha() const { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; return qobject_cast(d->node)->alphaChannelDisabled(); } void Node::setInheritAlpha(bool value) { if (!d->node) return; if (!d->node->inherits("KisLayer")) return; const_cast(qobject_cast(d->node))->disableAlphaChannel(value); } bool Node::locked() const { if (!d->node) return false; return d->node->userLocked(); } void Node::setLocked(bool value) { if (!d->node) return; d->node->setUserLocked(value); } QString Node::name() const { if (!d->node) return QString(); return d->node->name(); } void Node::setName(QString name) { if (!d->node) return; d->node->setName(name); } int Node::opacity() const { if (!d->node) return 0; return d->node->opacity(); } void Node::setOpacity(int value) { if (!d->node) return; if (value < 0) value = 0; if (value > 255) value = 255; d->node->setOpacity(value); } Node* Node::parentNode() const { if (!d->node) return 0; return new Node(d->image, d->node->parent()); } QString Node::type() const { if (!d->node) return QString(); return QString(); if (qobject_cast(d->node)) { return "paintlayer"; } else if (qobject_cast(d->node)) { return "grouplayer"; } if (qobject_cast(d->node)) { return "filelayer"; } if (qobject_cast(d->node)) { return "filterlayer"; } if (qobject_cast(d->node)) { return "filllayer"; } if (qobject_cast(d->node)) { return "clonelayer"; } if (qobject_cast(d->node)) { return "shapelayer"; } if (qobject_cast(d->node)) { return "transparencymask"; } if (qobject_cast(d->node)) { return "filtermask"; } if (qobject_cast(d->node)) { return "transformmask"; } if (qobject_cast(d->node)) { return "selectionmask"; } if (qobject_cast(d->node)) { return "colorizemask"; } } bool Node::visible() const { if (!d->node) return false; return d->node->visible();; } void Node::setVisible(bool visible) { if (!d->node) return; d->node->setVisible(visible); } QByteArray Node::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } +QByteArray Node::pixelDataAtTime(int x, int y, int w, int h, int time) const +{ + QByteArray ba; + + if (!d->node || !d->node->isAnimated()) return ba; + + // + KisRasterKeyframeChannel *rkc = dynamic_cast(d->node->getKeyframeChannel(KisKeyframeChannel::Content.id())); + if (!rkc) return ba; + KisKeyframeSP frame = rkc->keyframeAt(time); + if (!frame) return ba; + KisPaintDeviceSP dev = d->node->paintDevice(); + if (!dev) return ba; + + rkc->fetchFrame(frame, dev); + + ba.resize(w * h * dev->pixelSize()); + dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); + return ba; +} + QByteArray Node::projectionPixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } void Node::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->node) return; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } QRect Node::bounds() const { if (!d->node) return QRect(); return d->node->exactBounds(); } void Node::move(int x, int y) { if (!d->node) return; d->node->setX(x); d->node->setY(y); } QPoint Node::position() const { if (!d->node) return QPoint(); return QPoint(d->node->x(), d->node->y()); } bool Node::remove() { if (!d->node) return false; if (!d->node->parent()) return false; return d->image->removeNode(d->node); } Node* Node::duplicate() { if (!d->node) return 0; return new Node(d->image, d->node->clone()); } bool Node::save(const QString &filename, double xRes, double yRes) { if (!d->node) return false; if (filename.isEmpty()) return false; KisPaintDeviceSP projection = d->node->projection(); QRect bounds = d->node->exactBounds(); QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);; QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), projection->compositionSourceColorSpace(), d->node->name()); dst->setResolution(xRes, yRes); doc->setFileBatchMode(Krita::instance()->batchmode()); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", d->node->opacity()); paintLayer->paintDevice()->makeCloneFrom(projection, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); doc->setOutputMimeType(mimefilter.toLatin1()); bool r = doc->exportDocument(QUrl::fromLocalFile(filename)); if (!r) { qWarning() << doc->errorMessage(); } return r; } Node *Node::mergeDown() { if (!d->node) return 0; if (!qobject_cast(d->node.data())) return 0; if (!d->node->nextSibling()) return 0; if (!d->node->parent()) return 0; int index = d->node->parent()->index(d->node->prevSibling()); d->image->mergeDown(qobject_cast(d->node.data()), KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); d->image->waitForDone(); return new Node(d->image, d->node->parent()->at(index)); } +void Node::scaleNode(int width, int height, QString strategy) +{ + if (!d->node) return; + if (!qobject_cast(d->node.data())) return; + if (!d->node->parent()) return; + + KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); + if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); + + d->image->scaleNode(d->node, width, height, actualStrategy); +} + +void Node::rotateNode(double radians) +{ + if (!d->node) return; + if (!qobject_cast(d->node.data())) return; + if (!d->node->parent()) return; + + d->image->rotateNode(d->node, radians); +} + +void Node::cropNode(int x, int y, int w, int h) +{ + if (!d->node) return; + if (!qobject_cast(d->node.data())) return; + if (!d->node->parent()) return; + + QRect rect = QRect(x, y, w, h); + d->image->cropNode(d->node, rect); +} + +void Node::shearNode(double angleX, double angleY) +{ + if (!d->node) return; + if (!qobject_cast(d->node.data())) return; + if (!d->node->parent()) return; + + d->image->shearNode(d->node, angleX, angleY); +} + QImage Node::thumbnail(int w, int h) { if (!d->node) return QImage(); return d->node->createThumbnail(w, h); } KisPaintDeviceSP Node::paintDevice() const { return d->node->paintDevice(); } KisImageSP Node::image() const { return d->image; } KisNodeSP Node::node() const { return d->node; } diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h index f05103cb3c..17386c8474 100644 --- a/libs/libkis/Node.h +++ b/libs/libkis/Node.h @@ -1,471 +1,522 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This 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 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 LIBKIS_NODE_H #define LIBKIS_NODE_H #include #include #include "kritalibkis_export.h" #include "libkis.h" /** * Node represents a layer or mask in a Krita image's Node hierarchy. Group layers can contain * other layers and masks; layers can contain masks. * */ class KRITALIBKIS_EXPORT Node : public QObject { Q_OBJECT Q_DISABLE_COPY(Node) public: explicit Node(KisImageSP image, KisNodeSP node, QObject *parent = 0); ~Node() override; bool operator==(const Node &other) const; bool operator!=(const Node &other) const; public Q_SLOTS: /** * @brief alphaLocked checks whether the node is a paint layer and returns whether it is alpha locked * @return whether the paint layer is alpha locked, or false if the node is not a paint layer */ bool alphaLocked() const; /** * @brief setAlphaLocked set the layer to value if the the node is paint layer. */ void setAlphaLocked(bool value); /** * @return the blending mode of the layer. The values of the blending modes are defined in @see KoCompositeOpRegistry.h */ QString blendingMode() const; /** * @brief setBlendingMode set the blending mode of the node to the given value * @param value one of the string values from @see KoCompositeOpRegistry.h */ void setBlendingMode(QString value); /** * @brief channels creates a list of Channel objects that can be used individually to * show or hide certain channels, and to retrieve the contents of each channel in a * node separately. * * Only layers have channels, masks do not, and calling channels on a Node that is a mask * will return an empty list. * * @return the list of channels ordered in by position of the channels in pixel position */ QList channels() const; /** * Return a list of child nodes of the current node. The nodes are ordered from the bottommost up. * The function is not recursive. */ QList childNodes() const; /** * @brief addChildNode adds the given node in the list of children. * @param child the node to be added * @param above the node above which this node will be placed * @return false if adding the node failed */ bool addChildNode(Node *child, Node *above); /** * @brief removeChildNode removes the given node from the list of children. * @param child the node to be removed */ bool removeChildNode(Node *child); /** * @brief setChildNodes this replaces the existing set of child nodes with the new set. * @param nodes The list of nodes that will become children, bottom-up -- the first node, * is the bottom-most node in the stack. */ void setChildNodes(QList nodes); /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return if assigining the colorprofiel worked */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the node to the given colorspace * @param colorModel A string describing the color model of the node: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief Krita layers can be animated, i.e., have frames. * @return return true if the layer has frames. Currently, the scripting framework * does not give access to the animation features. */ bool animated() const; /** * @brief enableAnimation make the current layer animated, so it can have frames. */ void enableAnimation() const; /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ int colorLabel() const; /** * @brief setColorLabel sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. * @param index an integer corresponding to the set of available color labels. */ void setColorLabel(int index); /** * @brief inheritAlpha checks whether this node has the inherits alpha flag set * @return true if the Inherit Alpha is set */ bool inheritAlpha() const; /** * set the Inherit Alpha flag to the given value */ void setInheritAlpha(bool value); /** * @brief locked checkes whether the Node is locked. A locked node cannot be changed. * @return true if the Node is locked, false if it hasn't been locked. */ bool locked() const; /** * set the Locked flag to the give value */ void setLocked(bool value); /** * @return the user-visible name of this node. */ QString name() const; /** * rename the Node to the given name */ void setName(QString name); /** * return the opacity of the Node. The opacity is a value between 0 and 255. */ int opacity() const; /** * set the opacity of the Node to the given value. The opacity is a value between 0 and 255. */ void setOpacity(int value); /** * return the Node that is the parent of the current Node, or 0 if this is the root Node. */ Node* parentNode() const; /** * @brief type Krita has several types of nodes, split in layers and masks. Group * layers can contain other layers, any layer can contain masks. * * @return The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
  • colorizemask *
* * If the Node object isn't wrapping a valid Krita layer or mask object, and * empty string is returned. */ QString type() const; /** * Check whether the current Node is visible in the layer stack */ bool visible() const; /** * Set the visibility of the current node to @param visible */ void setVisible(bool visible); /** * @brief pixelData reads the given rectangle from the Node's paintable pixels, if those * exist, and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the node boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • GrayA: Gray, Alpha *
  • Selection: selectedness *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * If you read the pixeldata of a mask, a filter or generator layer, you get the selection bytes, * which is one channel with values in the range from 0..255. * * If you want to change the pixels of a node you can write the pixels back after manipulation * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups * or file layers. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; + /** + * @brief pixelDataAtTime a basic function to get pixeldata from an animated node at a given time. + * @param x the position from the left to start reading. + * @param y the position from the top to start reader + * @param w the row length to read + * @param h the number of rows to read + * @param time the frame number + * @return a QByteArray with the pixel data. The byte array may be empty. + */ + QByteArray pixelDataAtTime(int x, int y, int w, int h, int time) const; + /** * @brief projectionPixelData reads the given rectangle from the Node's projection (that is, what the node * looks like after all sub-Nodes (like layers in a group or masks on a layer) have been applied, * and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the node boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • GrayA: Gray, Alpha *
  • Selection: selectedness *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * If you read the projection of a mask, you get the selection bytes, which is one channel with * values in the range from 0..255. * * If you want to change the pixels of a node you can write the pixels back after manipulation * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups * or file layers. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray projectionPixelData(int x, int y, int w, int h) const; /** * @brief setPixelData writes the given bytes, of which there must be enough, into the * Node, if the Node has writable pixel data: * *
    *
  • paint layer: the layer's original pixels are overwritten *
  • filter layer, generator layer, any mask: the embedded selection's pixels are overwritten. * Note: for these *
* * File layers, Group layers, Clone layers cannot be written to. Calling setPixelData on * those layer types will silently do nothing. * * @param value the byte array representing the pixels. There must be enough bytes available. * Krita will take the raw pointer from the QByteArray and start reading, not stopping before * (number of channels * size of channel * w * h) bytes are read. * * @param x the x position to start writing from * @param y the y position to start writing from * @param w the width of each row * @param h the number of rows to write */ void setPixelData(QByteArray value, int x, int y, int w, int h); /** * @brief bounds return the exact bounds of the node's paint device * @return the bounds, or an empty QRect if the node has no paint device or is empty. */ QRect bounds() const; /** * move the pixels to the given x, y location in the image coordinate space. */ void move(int x, int y); /** * @brief position returns the position of the paint device of this node * @return the top-left position of the node */ QPoint position() const; /** * @brief remove removes this node from its parent image. */ bool remove(); /** * @brief duplicate returns a full copy of the current node. The node is not inserted in the graphc * @return a valid Node object or 0 if the node couldn't be duplicated. */ Node* duplicate(); /** * @brief save exports the given node with this filename. The extension of the filename determins the filetype. * @param filename the filename including extension * @param xRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @param yRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @return true if saving succeeded, false if it failed. */ bool save(const QString &filename, double xRes, double yRes); /** * @brief mergeDown merges the given node with the first visible node underneath this node in the layerstack. * This will drop all per-layer metadata. * @param node the node to merge down; this node will be removed from the layer stack */ Node *mergeDown(); + /** + * @brief scaleNode + * @param width + * @param height + * @param strategy the scaling strategy. There's several ones amongst these that aren't available in the regular UI. + *
    + *
  • Hermite
  • + *
  • Bicubic - Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.
  • + *
  • Box - Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.
  • + *
  • Bilinear - Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.
  • + *
  • Bell
  • + *
  • BSpline
  • + *
  • Lanczos3 - Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.
  • + *
  • Mitchell
  • + *
+ */ + void scaleNode(int width, int height, QString strategy); + + /** + * @brief rotateNode rotate this layer by the given radians. + * @param radians amount the layer should be rotated in, in radians. + */ + void rotateNode(double radians); + + /** + * @brief cropNode crop this layer. + * @param x the left edge of the cropping rectangle. + * @param y the top edge of the cropping rectangle + * @param w the right edge of the cropping rectangle + * @param h the bottom edge of the cropping rectangle + */ + void cropNode(int x, int y, int w, int h); + + /** + * @brief shearNode perform a shear operation on this node. + * @param angleX the X-angle in degrees to shear by + * @param angleY the Y-angle in degrees to shear by + */ + void shearNode(double angleX, double angleY); + /** * @brief thumbnail create a thumbnail of the given dimensions. The thumbnail is sized according * to the layer dimensions, not the image dimensions. If the requested size is too big a null * QImage is created. If the current node cannot generate a thumbnail, a transparent QImage of the * requested size is generated. * @return a QImage representing the layer contents. */ QImage thumbnail(int w, int h); private: friend class Filter; friend class Document; friend class Selection; /** * @brief paintDevice gives access to the internal paint device of this Node * @return the paintdevice or 0 if the node does not have an editable paint device. */ KisPaintDeviceSP paintDevice() const; KisImageSP image() const; KisNodeSP node() const; struct Private; Private *const d; }; #endif // LIBKIS_NODE_H diff --git a/libs/odf/KoColumns.h b/libs/odf/KoColumns.h index dc6b4e829e..cf068a0cf9 100644 --- a/libs/odf/KoColumns.h +++ b/libs/odf/KoColumns.h @@ -1,137 +1,138 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright 2002, 2003 David Faure Copyright 2003 Nicolas GOUTTE Copyright 2007 Thomas Zander Copyright 2012 Friedrich W. H. Kossebau 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 KOCOLUMNS_H #define KOCOLUMNS_H #include "kritaodf_export.h" #include #include +#include + class KoGenStyle; -class KoXmlElement; /** structure for columns */ struct KoColumns { enum SeparatorVerticalAlignment { AlignTop = Qt::AlignTop, AlignVCenter = Qt::AlignVCenter, AlignBottom = Qt::AlignBottom }; enum SeparatorStyle { None = Qt::NoPen, Solid = Qt::SolidLine, Dashed = Qt::DashLine, Dotted = Qt::DotLine, DotDashed = Qt::DashDotLine }; struct ColumnDatum { /** Left indent in points */ qreal leftMargin; /** Right indent in points */ qreal rightMargin; /** Top indent in points */ qreal topMargin; /** Bottom indent in points */ qreal bottomMargin; /** The relative width */ int relativeWidth; ColumnDatum() {} ColumnDatum(qreal lm, qreal rm, qreal tm, qreal bm, int rw) : leftMargin(lm), rightMargin(rm), topMargin(tm), bottomMargin(bm), relativeWidth(rw) {} bool operator==(const KoColumns::ColumnDatum &rhs) const { return (leftMargin == rhs.leftMargin) && (rightMargin == rhs.rightMargin) && (topMargin == rhs.topMargin) && (bottomMargin == rhs.bottomMargin) && (relativeWidth == rhs.relativeWidth); } }; /** Number of columns */ int count; /** Spacing between columns in points */ qreal gapWidth; SeparatorStyle separatorStyle; QColor separatorColor; SeparatorVerticalAlignment separatorVerticalAlignment; /** Width in pt */ qreal separatorWidth; /** Height in percent. Default is 100% */ unsigned int separatorHeight; /** data about the individual columns if there */ QList columnData; /** * Construct a columns with the default column count 1, * default margins (2 cm), and portrait orientation. */ KRITAODF_EXPORT KoColumns(); KRITAODF_EXPORT void reset(); KRITAODF_EXPORT bool operator==(const KoColumns &l) const; KRITAODF_EXPORT bool operator!=(const KoColumns &l) const; /** * Save this columns to ODF. */ KRITAODF_EXPORT void saveOdf(KoGenStyle &style) const; /** * Load this columns from ODF */ KRITAODF_EXPORT void loadOdf(const KoXmlElement &style); qreal totalRelativeWidth() const { qreal result = 0.0; Q_FOREACH (const ColumnDatum &c, columnData) { result += c.relativeWidth; } return result; } KRITAODF_EXPORT static const char * separatorStyleString(KoColumns::SeparatorStyle separatorStyle); KRITAODF_EXPORT static const char * separatorVerticalAlignmentString(KoColumns::SeparatorVerticalAlignment separatorVerticalAlignment); KRITAODF_EXPORT static KoColumns::SeparatorVerticalAlignment parseSeparatorVerticalAlignment(const QString &value); KRITAODF_EXPORT static QColor parseSeparatorColor(const QString &value); KRITAODF_EXPORT static int parseSeparatorHeight(const QString &value); KRITAODF_EXPORT static KoColumns::SeparatorStyle parseSeparatorStyle(const QString &value); KRITAODF_EXPORT static int parseRelativeWidth(const QString &value); }; #endif /* KOCOLUMNS_H */ diff --git a/libs/odf/KoOdfReadStore.cpp b/libs/odf/KoOdfReadStore.cpp index c02e88d0df..505dd7d138 100644 --- a/libs/odf/KoOdfReadStore.cpp +++ b/libs/odf/KoOdfReadStore.cpp @@ -1,145 +1,141 @@ /* This file is part of the KDE project Copyright (C) 2005 David Faure Copyright (C) 2007 Thorsten Zach3n 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 "KoOdfReadStore.h" #include #include #include #include #include "KoOdfStylesReader.h" #include class Q_DECL_HIDDEN KoOdfReadStore::Private { public: Private(KoStore *s) : store(s) { } KoStore * store; KoOdfStylesReader stylesReader; // it is needed to keep the stylesDoc around so that you can access the styles KoXmlDocument stylesDoc; KoXmlDocument contentDoc; KoXmlDocument settingsDoc; }; KoOdfReadStore::KoOdfReadStore(KoStore *store) : d(new Private(store)) { } KoOdfReadStore::~KoOdfReadStore() { delete d; } KoStore * KoOdfReadStore::store() const { return d->store; } KoOdfStylesReader &KoOdfReadStore::styles() { return d->stylesReader; } KoXmlDocument KoOdfReadStore::contentDoc() const { return d->contentDoc; } KoXmlDocument KoOdfReadStore::settingsDoc() const { return d->settingsDoc; } bool KoOdfReadStore::loadAndParse(QString &errorMessage) { if (!loadAndParse("content.xml", d->contentDoc, errorMessage)) { return false; } if (d->store->hasFile("styles.xml")) { if (!loadAndParse("styles.xml", d->stylesDoc, errorMessage)) { return false; } } // Load styles from style.xml d->stylesReader.createStyleMap(d->stylesDoc, true); // Also load styles from content.xml d->stylesReader.createStyleMap(d->contentDoc, false); if (d->store->hasFile("settings.xml")) { loadAndParse("settings.xml", d->settingsDoc, errorMessage); } return true; } bool KoOdfReadStore::loadAndParse(const QString &fileName, KoXmlDocument &doc, QString &errorMessage) { if (!d->store) { errorMessage = i18n("No store backend"); return false; } if (!d->store->isOpen()) { if (!d->store->open(fileName)) { debugOdf << "Entry " << fileName << " not found!"; // not a warning as embedded stores don't have to have all files errorMessage = i18n("Could not find %1", fileName); return false; } } bool ok = loadAndParse(d->store->device(), doc, errorMessage, fileName); d->store->close(); return ok; } bool KoOdfReadStore::loadAndParse(QIODevice *fileDevice, KoXmlDocument &doc, QString &errorMessage, const QString &fileName) { // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; if (!fileDevice->isOpen()) { fileDevice->open(QIODevice::ReadOnly); } - - QXmlStreamReader reader(fileDevice); - reader.setNamespaceProcessing(true); - - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(fileDevice, true, &errorMsg, &errorLine, &errorColumn); if (!ok) { errorOdf << "Parsing error in " << fileName << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errorMessage = i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } else { debugOdf << "File" << fileName << " loaded and parsed"; } return ok; } diff --git a/libs/odf/tests/CMakeLists.txt b/libs/odf/tests/CMakeLists.txt index 2c81799249..9dbf046647 100644 --- a/libs/odf/tests/CMakeLists.txt +++ b/libs/odf/tests/CMakeLists.txt @@ -1,23 +1,22 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include(ECMAddTests) include(KritaAddBrokenUnitTest) ecm_add_tests( TestKoGenStyles.cpp TestOdfSettings.cpp TestKoOdfLoadingContext.cpp TestStorage.cpp NAME_PREFIX "libs-odf-" LINK_LIBRARIES kritaodf KF5::I18n Qt5::Test) ecm_add_tests( TestXmlWriter.cpp TestXmlReader.cpp - TestXmlReaderWithoutSpaces.cpp kodomtest.cpp TestKoUnit.cpp TestNumberStyle.cpp TestKoElementReference.cpp NAME_PREFIX "libs-odf-" LINK_LIBRARIES kritaodf Qt5::Test) diff --git a/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp b/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp deleted file mode 100644 index 1d3449ed3e..0000000000 --- a/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp +++ /dev/null @@ -1,2696 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include - - -class TestXmlReaderWithoutSpaces : public QObject -{ - Q_OBJECT -private Q_SLOTS: - void testNode(); - void testElement(); - void testAttributes(); - void testText(); - void testCDATA(); - void testDocument(); - void testDocumentType(); - void testNamespace(); - void testParseQString(); - void testUnload(); - void testSimpleXML(); - void testRootError(); - void testMismatchedTag(); - void testConvertQDomDocument(); - void testConvertQDomElement(); - void testSimpleOpenDocumentText(); - void testWhitespace(); - void testSimpleOpenDocumentSpreadsheet(); - void testSimpleOpenDocumentPresentation(); - void testSimpleOpenDocumentFormula(); - void testLargeOpenDocumentSpreadsheet(); - void testExternalOpenDocumentSpreadsheet(const QString& filename); -}; - -void TestXmlReaderWithoutSpaces::testNode() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // null node - KoXmlNode node1; - QCOMPARE(node1.nodeName(), QString()); - QCOMPARE(node1.isNull(), true); - QCOMPARE(node1.isElement(), false); - QCOMPARE(node1.isDocument(), false); - QCOMPARE(node1.ownerDocument().isNull(), true); - QCOMPARE(node1.parentNode().isNull(), true); - QCOMPARE(node1.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(node1), 0); - QCOMPARE(node1.firstChild().isNull(), true); - QCOMPARE(node1.lastChild().isNull(), true); - QCOMPARE(node1.previousSibling().isNull(), true); - QCOMPARE(node1.nextSibling().isNull(), true); - - // compare with another null node - KoXmlNode node2; - QCOMPARE(node2.isNull(), true); - QCOMPARE(node1 == node2, true); - QCOMPARE(node1 != node2, false); - - // a node which is a document - KoXmlNode node3 = doc; - QCOMPARE(node3.nodeName(), QString("#document")); - QCOMPARE(node3.isNull(), false); - QCOMPARE(node3.isElement(), false); - QCOMPARE(node3.isText(), false); - QCOMPARE(node3.isDocument(), true); - QCOMPARE(node3.ownerDocument().isNull(), false); - QCOMPARE(node3.ownerDocument() == doc, true); - QCOMPARE(node3.toDocument() == doc, true); - QCOMPARE(KoXml::childNodesCount(node3), 1); - - // convert to document and the compare - KoXmlDocument doc2 = node3.toDocument(); - QCOMPARE(doc2.nodeName(), QString("#document")); - QCOMPARE(doc2.isNull(), false); - QCOMPARE(doc2.isDocument(), true); - QCOMPARE(node3 == doc2, true); - QCOMPARE(KoXml::childNodesCount(doc2), 1); - - // a document is of course can't be converted to element - KoXmlElement invalidElement = node3.toElement(); - QCOMPARE(invalidElement.nodeName(), QString()); - QCOMPARE(invalidElement.isNull(), true); - QCOMPARE(invalidElement.isElement(), false); - QCOMPARE(invalidElement.isText(), false); - QCOMPARE(invalidElement.isDocument(), false); - - // clear() makes it a null node again - node3.clear(); - QCOMPARE(node3.isNull(), true); - QCOMPARE(node3.nodeName(), QString()); - QCOMPARE(node3.isElement(), false); - QCOMPARE(node3.isText(), false); - QCOMPARE(node3.isDocument(), false); - QCOMPARE(node3.ownerDocument().isNull(), true); - QCOMPARE(node1 == node3, true); - QCOMPARE(node1 != node3, false); - - // a node which is an element for - KoXmlNode node4 = doc.firstChild(); - QCOMPARE(node4.isNull(), false); - QCOMPARE(node4.isElement(), true); - QCOMPARE(node4.isText(), false); - QCOMPARE(node4.isDocument(), false); - QCOMPARE(node4.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(node4), 2); - QCOMPARE(node4.ownerDocument() == doc, true); - QCOMPARE(node4.toElement() == doc.firstChild().toElement(), true); - - // clear() makes it a null node again - node4.clear(); - QCOMPARE(node4.isNull(), true); - QCOMPARE(node4.isElement(), false); - QCOMPARE(node4.isText(), false); - QCOMPARE(node4.isDocument(), false); - QCOMPARE(node4 == node1, true); - QCOMPARE(node4 != node1, false); - QCOMPARE(KoXml::childNodesCount(node4), 0); - - // a node which is an element for - KoXmlNode node5 = doc.firstChild().firstChild(); - QCOMPARE(node5.nodeName(), QString("continents")); - QCOMPARE(node5.isNull(), false); - QCOMPARE(node5.isElement(), true); - QCOMPARE(node5.isText(), false); - QCOMPARE(node5.isDocument(), false); - QCOMPARE(node5.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(node5), 6); - QCOMPARE(node5.ownerDocument() == doc, true); - - // convert to element and the compare - KoXmlElement continentsElement = node5.toElement(); - QCOMPARE(node5 == continentsElement, true); - QCOMPARE(continentsElement.isNull(), false); - QCOMPARE(continentsElement.isElement(), true); - QCOMPARE(continentsElement.isText(), false); - QCOMPARE(continentsElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(continentsElement), 6); - QCOMPARE(continentsElement.ownerDocument() == doc, true); - - // and it doesn't make sense to convert that node to document - KoXmlDocument invalidDoc = node5.toDocument(); - QCOMPARE(invalidDoc.isNull(), true); - QCOMPARE(invalidDoc.isElement(), false); - QCOMPARE(invalidDoc.isText(), false); - QCOMPARE(invalidDoc.isDocument(), false); - - // node for using namedItem() function - KoXmlNode europeNode = continentsElement.namedItem(QString("europe")); - QCOMPARE(europeNode.nodeName(), QString("europe")); - QCOMPARE(europeNode.isNull(), false); - QCOMPARE(europeNode.isElement(), true); - QCOMPARE(europeNode.isText(), false); - QCOMPARE(europeNode.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(europeNode), 0); - QCOMPARE(europeNode.ownerDocument() == doc, true); - - // search non-existing node - KoXmlNode fooNode = continentsElement.namedItem(QString("foobar")); - QCOMPARE(fooNode.isNull(), true); - QCOMPARE(fooNode.isElement(), false); - QCOMPARE(fooNode.isText(), false); - QCOMPARE(fooNode.isCDATASection(), false); - QCOMPARE(KoXml::childNodesCount(fooNode), 0); -} - -void TestXmlReaderWithoutSpaces::testElement() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << ""; - xmlstream << "

"; - xmlstream << "Hello, world!"; - xmlstream << "

"; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // element for - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.nodeName(), QString("html")); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.isDocument(), false); - QCOMPARE(rootElement.ownerDocument().isNull(), false); - QCOMPARE(rootElement.ownerDocument() == doc, true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.parentNode().toDocument() == doc, true); - QCOMPARE(rootElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 1); - QCOMPARE(rootElement.tagName(), QString("html")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // element for - KoXmlElement bodyElement; - bodyElement = rootElement.firstChild().toElement(); - QCOMPARE(bodyElement.nodeName(), QString("body")); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.isDocument(), false); - QCOMPARE(bodyElement.ownerDocument().isNull(), false); - QCOMPARE(bodyElement.ownerDocument() == doc, true); - QCOMPARE(bodyElement.parentNode().isNull(), false); - QCOMPARE(bodyElement.parentNode() == rootElement, true); - QCOMPARE(bodyElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(bodyElement), 1); - QCOMPARE(bodyElement.tagName(), QString("body")); - QCOMPARE(bodyElement.prefix().isNull(), true); - QCOMPARE(bodyElement.hasAttribute("bgcolor"), true); - QCOMPARE(bodyElement.attribute("bgcolor"), QString("#000")); - - // a shared copy of , will still have access to attribute bgcolor - KoXmlElement body2Element; - body2Element = bodyElement; - QCOMPARE(body2Element.nodeName(), QString("body")); - QCOMPARE(body2Element.isNull(), false); - QCOMPARE(body2Element.isElement(), true); - QCOMPARE(body2Element.isDocument(), false); - QCOMPARE(body2Element.ownerDocument().isNull(), false); - QCOMPARE(body2Element.ownerDocument() == doc, true); - QCOMPARE(body2Element == bodyElement, true); - QCOMPARE(body2Element != bodyElement, false); - QCOMPARE(body2Element.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(body2Element), 1); - QCOMPARE(body2Element.tagName(), QString("body")); - QCOMPARE(body2Element.prefix().isNull(), true); - QCOMPARE(body2Element.hasAttribute("bgcolor"), true); - QCOMPARE(body2Element.attribute("bgcolor"), QString("#000")); - - // empty element, by default constructor - KoXmlElement testElement; - QCOMPARE(testElement.nodeName(), QString()); - QCOMPARE(testElement.isNull(), true); - QCOMPARE(testElement.isElement(), false); - QCOMPARE(testElement.isDocument(), false); - QCOMPARE(testElement.ownerDocument().isNull(), true); - QCOMPARE(testElement.ownerDocument() != doc, true); - QCOMPARE(testElement == rootElement, false); - QCOMPARE(testElement != rootElement, true); - QCOMPARE(testElement.parentNode().isNull(), true); - QCOMPARE(testElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(testElement), 0); - - // check assignment operator - testElement = rootElement; - QCOMPARE(testElement.nodeName(), QString("html")); - QCOMPARE(testElement.isNull(), false); - QCOMPARE(testElement.isElement(), true); - QCOMPARE(testElement.isDocument(), false); - QCOMPARE(testElement == rootElement, true); - QCOMPARE(testElement != rootElement, false); - QCOMPARE(testElement.parentNode().isNull(), false); - QCOMPARE(testElement.parentNode().toDocument() == doc, true); - QCOMPARE(testElement.tagName(), QString("html")); - QCOMPARE(testElement.prefix().isNull(), true); - QCOMPARE(KoXml::childNodesCount(testElement), 1); - - // assigned from another empty element - testElement = KoXmlElement(); - QCOMPARE(testElement.isNull(), true); - QCOMPARE(testElement != rootElement, true); - - // assigned from - testElement = bodyElement; - QCOMPARE(testElement.isNull(), false); - QCOMPARE(testElement.isElement(), true); - QCOMPARE(testElement.isDocument(), false); - QCOMPARE(testElement.ownerDocument().isNull(), false); - QCOMPARE(testElement.ownerDocument() == doc, true); - QCOMPARE(testElement == bodyElement, true); - QCOMPARE(testElement.parentNode().isNull(), false); - QCOMPARE(testElement.tagName(), QString("body")); - QCOMPARE(testElement.prefix().isNull(), true); - QCOMPARE(testElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(testElement), 1); - - // copy constructor - KoXmlElement dummyElement(rootElement); - QCOMPARE(dummyElement.isNull(), false); - QCOMPARE(dummyElement.isElement(), true); - QCOMPARE(dummyElement.isDocument(), false); - QCOMPARE(dummyElement.ownerDocument().isNull(), false); - QCOMPARE(dummyElement.ownerDocument() == doc, true); - QCOMPARE(dummyElement == rootElement, true); - QCOMPARE(dummyElement.parentNode().isNull(), false); - QCOMPARE(dummyElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(dummyElement), 1); - QCOMPARE(dummyElement.tagName(), QString("html")); - QCOMPARE(dummyElement.prefix().isNull(), true); - - // clear() turns element to null node - dummyElement.clear(); - QCOMPARE(dummyElement.isNull(), true); - QCOMPARE(dummyElement.isElement(), false); - QCOMPARE(dummyElement.isDocument(), false); - QCOMPARE(dummyElement.ownerDocument().isNull(), true); - QCOMPARE(dummyElement.ownerDocument() == doc, false); - QCOMPARE(dummyElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(dummyElement), 0); - QCOMPARE(dummyElement == rootElement, false); - QCOMPARE(dummyElement != rootElement, true); - - // check for plain null node converted to element - KoXmlNode dummyNode; - dummyElement = dummyNode.toElement(); - QCOMPARE(dummyElement.isNull(), true); - QCOMPARE(dummyElement.isElement(), false); - QCOMPARE(dummyElement.isDocument(), false); - QCOMPARE(dummyElement.ownerDocument().isNull(), true); - QCOMPARE(dummyElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(dummyElement), 0); - QCOMPARE(dummyElement.ownerDocument() == doc, false); -} - -void TestXmlReaderWithoutSpaces::testAttributes() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << "

"; - xmlstream << ""; - xmlstream << "

"; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.parentNode().toDocument() == doc, true); - QCOMPARE(rootElement.tagName(), QString("p")); - QCOMPARE(rootElement.prefix().isNull(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 1); - - KoXmlElement imgElement; - imgElement = rootElement.firstChild().toElement(); - QCOMPARE(imgElement.isNull(), false); - QCOMPARE(imgElement.isElement(), true); - QCOMPARE(imgElement.tagName(), QString("img")); - QCOMPARE(imgElement.prefix().isNull(), true); - QCOMPARE(KoXml::childNodesCount(imgElement), 0); - QCOMPARE(imgElement.hasAttribute("src"), true); - QCOMPARE(imgElement.hasAttribute("width"), true); - QCOMPARE(imgElement.hasAttribute("height"), true); - QCOMPARE(imgElement.hasAttribute("non-exist"), false); - QCOMPARE(imgElement.hasAttribute("SRC"), false); - QCOMPARE(imgElement.attribute("src"), QString("foo.png")); - QCOMPARE(imgElement.attribute("width"), QString("300")); - QCOMPARE(imgElement.attribute("width").toInt(), 300); - QCOMPARE(imgElement.attribute("height"), QString("150")); - QCOMPARE(imgElement.attribute("height").toInt(), 150); - QCOMPARE(imgElement.attribute("border").isEmpty(), true); - QCOMPARE(imgElement.attribute("border", "0").toInt(), 0); - QCOMPARE(imgElement.attribute("border", "-1").toInt(), -1); - - QStringList list = KoXml::attributeNames(imgElement); - QCOMPARE(list.count(), 3); - QVERIFY(list.contains("src")); - QVERIFY(list.contains("width")); - QVERIFY(list.contains("height")); - QVERIFY(! list.contains("border")); - Q_FOREACH (QString a, list) { - QCOMPARE(imgElement.hasAttribute(a), true); - QCOMPARE(imgElement.attribute(a).isEmpty(), false); - } -} - -void TestXmlReaderWithoutSpaces::testText() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << "

"; - xmlstream << "Hello "; - xmlstream << "world"; - xmlstream << "

"; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // element for

- KoXmlElement parElement; - parElement = doc.documentElement(); - QCOMPARE(parElement.isNull(), false); - QCOMPARE(parElement.isElement(), true); - QCOMPARE(parElement.isText(), false); - QCOMPARE(parElement.isDocument(), false); - QCOMPARE(parElement.ownerDocument().isNull(), false); - QCOMPARE(parElement.ownerDocument() == doc, true); - QCOMPARE(parElement.parentNode().isNull(), false); - QCOMPARE(parElement.parentNode().toDocument() == doc, true); - QCOMPARE(parElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(parElement), 2); // and text node "Hello " - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.prefix().isNull(), true); - QCOMPARE(parElement.text(), QString("Hello world")); - - // node for "Hello" - KoXmlNode helloNode; - helloNode = parElement.firstChild(); - QCOMPARE(helloNode.nodeName(), QString("#text")); - QCOMPARE(helloNode.isNull(), false); - QCOMPARE(helloNode.isElement(), false); - QCOMPARE(helloNode.isText(), true); - QCOMPARE(helloNode.isDocument(), false); - QCOMPARE(helloNode.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(helloNode), 0); - - // "Hello" text - KoXmlText helloText; - helloText = helloNode.toText(); - QCOMPARE(helloText.nodeName(), QString("#text")); - QCOMPARE(helloText.isNull(), false); - QCOMPARE(helloText.isElement(), false); - QCOMPARE(helloText.isText(), true); - QCOMPARE(helloText.isDocument(), false); - QCOMPARE(helloText.data(), QString("Hello ")); - QCOMPARE(KoXml::childNodesCount(helloText), 0); - - // shared copy of the text - KoXmlText hello2Text; - hello2Text = helloText; - QCOMPARE(hello2Text.isNull(), false); - QCOMPARE(hello2Text.isElement(), false); - QCOMPARE(hello2Text.isText(), true); - QCOMPARE(hello2Text.isDocument(), false); - QCOMPARE(hello2Text.data(), QString("Hello ")); - QCOMPARE(KoXml::childNodesCount(hello2Text), 0); - - // element for - KoXmlElement boldElement; - boldElement = helloNode.nextSibling().toElement(); - QCOMPARE(boldElement.isNull(), false); - QCOMPARE(boldElement.isElement(), true); - QCOMPARE(boldElement.isText(), false); - QCOMPARE(boldElement.isDocument(), false); - QCOMPARE(boldElement.ownerDocument().isNull(), false); - QCOMPARE(boldElement.ownerDocument() == doc, true); - QCOMPARE(boldElement.parentNode().isNull(), false); - QCOMPARE(boldElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(boldElement), 1); // text node "world" - QCOMPARE(boldElement.tagName(), QString("b")); - QCOMPARE(boldElement.prefix().isNull(), true); - - // "world" text - KoXmlText worldText; - worldText = boldElement.firstChild().toText(); - QCOMPARE(worldText.isNull(), false); - QCOMPARE(worldText.isElement(), false); - QCOMPARE(worldText.isText(), true); - QCOMPARE(worldText.isDocument(), false); - QCOMPARE(worldText.data(), QString("world")); - QCOMPARE(KoXml::childNodesCount(worldText), 0); -} - -void TestXmlReaderWithoutSpaces::testCDATA() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << "

"; - xmlstream << "Hello "; - xmlstream << ""; - xmlstream << "

"; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // element for

- KoXmlElement parElement; - parElement = doc.documentElement(); - QCOMPARE(parElement.isNull(), false); - QCOMPARE(parElement.isElement(), true); - QCOMPARE(parElement.isText(), false); - QCOMPARE(parElement.isDocument(), false); - QCOMPARE(parElement.ownerDocument().isNull(), false); - QCOMPARE(parElement.ownerDocument() == doc, true); - QCOMPARE(parElement.parentNode().isNull(), false); - QCOMPARE(parElement.parentNode().toDocument() == doc, true); - QCOMPARE(parElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(parElement), 2); - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.prefix().isNull(), true); - QCOMPARE(parElement.text(), QString("Hello world")); - - // node for "Hello" - KoXmlNode helloNode; - helloNode = parElement.firstChild(); - QCOMPARE(helloNode.isNull(), false); - QCOMPARE(helloNode.isElement(), false); - QCOMPARE(helloNode.isText(), true); - QCOMPARE(helloNode.isDocument(), false); - - // "Hello" text - KoXmlText helloText; - helloText = helloNode.toText(); - QCOMPARE(helloText.isNull(), false); - QCOMPARE(helloText.isElement(), false); - QCOMPARE(helloText.isText(), true); - QCOMPARE(helloText.isDocument(), false); - QCOMPARE(helloText.data(), QString("Hello ")); - - // node for CDATA "world!" - // Note: isText() is also true for CDATA - KoXmlNode worldNode; - worldNode = helloNode.nextSibling(); - QCOMPARE(worldNode.nodeName(), QString("#cdata-section")); - QCOMPARE(worldNode.isNull(), false); - QCOMPARE(worldNode.isElement(), false); - QCOMPARE(worldNode.isText(), true); - QCOMPARE(worldNode.isCDATASection(), true); - QCOMPARE(worldNode.isDocument(), false); - - // CDATA section for "world!" - // Note: isText() is also true for CDATA - KoXmlCDATASection worldCDATA; - worldCDATA = worldNode.toCDATASection(); - QCOMPARE(worldCDATA.nodeName(), QString("#cdata-section")); - QCOMPARE(worldCDATA.isNull(), false); - QCOMPARE(worldCDATA.isElement(), false); - QCOMPARE(worldCDATA.isText(), true); - QCOMPARE(worldCDATA.isCDATASection(), true); - QCOMPARE(worldCDATA.isDocument(), false); - QCOMPARE(worldCDATA.data(), QString("world")); -} - -void TestXmlReaderWithoutSpaces::testDocument() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << "\n"; - xmlstream << "\n"; - xmlstream << "\n"; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - - // empty document - QCOMPARE(doc.nodeName(), QString()); - QCOMPARE(doc.isNull(), true); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), false); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), true); - QCOMPARE(doc.lastChild().isNull(), true); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // now give something as the content - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // this document has something already - QCOMPARE(doc.nodeName(), QString("#document")); - QCOMPARE(doc.isNull(), false); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), true); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), false); - QCOMPARE(doc.lastChild().isNull(), false); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // make sure its children are fine - KoXmlElement rootElement; - rootElement = doc.firstChild().toElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.isDocument(), false); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.parentNode().toDocument() == doc, true); - rootElement = doc.lastChild().toElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.isDocument(), false); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.parentNode().toDocument() == doc, true); - - // clear() converts it into null node - doc.clear(); - QCOMPARE(doc.nodeName(), QString()); - QCOMPARE(doc.isNull(), true); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), false); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), true); - QCOMPARE(doc.lastChild().isNull(), true); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // assigned from another empty document - doc = KoXmlDocument(); - QCOMPARE(doc.nodeName(), QString()); - QCOMPARE(doc.nodeName().isEmpty(), true); - QCOMPARE(doc.isNull(), true); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), false); - QCOMPARE(doc.parentNode().isNull(), true); -} - -void TestXmlReaderWithoutSpaces::testDocumentType() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "

"; - xmlstream << "

"; - xmlstream << ""; - xmldevice.close(); - - // empty document - KoXmlDocument doc(false); - QCOMPARE(doc.nodeName(), QString()); - QCOMPARE(doc.isNull(), true); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), false); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), true); - QCOMPARE(doc.lastChild().isNull(), true); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // has empty doctype - KoXmlDocumentType doctype = doc.doctype(); - QCOMPARE(doctype.nodeName(), QString()); - QCOMPARE(doctype.isNull(), true); - QCOMPARE(doctype.isElement(), false); - QCOMPARE(doctype.isDocument(), false); - QCOMPARE(doctype.isDocumentType(), false); - QCOMPARE(doctype.parentNode().isNull(), true); - QCOMPARE(doctype.firstChild().isNull(), true); - QCOMPARE(doctype.lastChild().isNull(), true); - QCOMPARE(doctype.previousSibling().isNull(), true); - QCOMPARE(doctype.nextSibling().isNull(), true); - - // now give something as the content - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // this document has something already - QCOMPARE(doc.nodeName(), QString("#document")); - QCOMPARE(doc.isNull(), false); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), true); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), false); - QCOMPARE(doc.lastChild().isNull(), false); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // the doctype becomes a valid one - doctype = doc.doctype(); - QCOMPARE(doctype.nodeName(), QString("html")); - QCOMPARE(doctype.name(), QString("html")); - QCOMPARE(doctype.isNull(), false); - QCOMPARE(doctype.isElement(), false); - QCOMPARE(doctype.isDocument(), false); - QCOMPARE(doctype.isDocumentType(), true); - QCOMPARE(doctype.parentNode().isNull(), false); - QCOMPARE(doctype.parentNode() == doc, true); - QCOMPARE(doctype.firstChild().isNull(), true); - QCOMPARE(doctype.lastChild().isNull(), true); - QCOMPARE(doctype.previousSibling().isNull(), true); - QCOMPARE(doctype.nextSibling().isNull(), true); -} - -void TestXmlReaderWithoutSpaces::testNamespace() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // taken from example in Qt documentation (xml.html) - xmlstream << ""; - xmlstream << ""; - xmlstream << "Practical XML"; - xmlstream << ""; - xmlstream << ""; - xmlstream << "A Namespace Called fnord"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - KoXmlElement rootElement; - KoXmlElement bookElement; - KoXmlElement bookTitleElement; - KoXmlElement bookAuthorElement; - - // ------------- first without any namespace processing ----------- - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.tagName(), QString("document")); - QCOMPARE(rootElement.prefix().isNull(), true); - - bookElement = rootElement.firstChild().toElement(); - QCOMPARE(bookElement.isNull(), false); - QCOMPARE(bookElement.isElement(), true); - QCOMPARE(bookElement.tagName(), QString("book")); - QCOMPARE(bookElement.prefix().isNull(), true); - QCOMPARE(bookElement.localName(), QString()); - - bookTitleElement = bookElement.firstChild().toElement(); - QCOMPARE(bookTitleElement.isNull(), false); - QCOMPARE(bookTitleElement.isElement(), true); - QCOMPARE(bookTitleElement.tagName(), QString("book:title")); - QCOMPARE(bookTitleElement.prefix().isNull(), true); - QCOMPARE(bookTitleElement.localName(), QString()); - - bookAuthorElement = bookTitleElement.nextSibling().toElement(); - QCOMPARE(bookAuthorElement.isNull(), false); - QCOMPARE(bookAuthorElement.isElement(), true); - QCOMPARE(bookAuthorElement.tagName(), QString("book:author")); - QCOMPARE(bookAuthorElement.prefix().isNull(), true); - QCOMPARE(bookAuthorElement.attribute("title"), QString("Ms")); - QCOMPARE(bookAuthorElement.attribute("fnord:title"), QString("Goddess")); - QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); - - // ------------- now with namespace processing ----------- - xmldevice.seek(0); // just to rewind - - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* defaultNS = "http://trolltech.com/fnord/"; - const char* bookNS = "http://trolltech.com/fnord/book/"; - const char* fnordNS = "http://trolltech.com/fnord/"; - - // - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.tagName(), QString("document")); - QCOMPARE(rootElement.prefix().isEmpty(), true); - QCOMPARE(rootElement.namespaceURI(), QString(defaultNS)); - QCOMPARE(rootElement.localName(), QString("document")); - - // - bookElement = rootElement.firstChild().toElement(); - QCOMPARE(bookElement.isNull(), false); - QCOMPARE(bookElement.isElement(), true); - QCOMPARE(bookElement.tagName(), QString("book")); - QCOMPARE(bookElement.prefix().isEmpty(), true); - QCOMPARE(bookElement.namespaceURI(), QString(defaultNS)); - QCOMPARE(bookElement.localName(), QString("book")); - - // - bookTitleElement = bookElement.firstChild().toElement(); - QCOMPARE(bookTitleElement.isNull(), false); - QCOMPARE(bookTitleElement.isElement(), true); - QCOMPARE(bookTitleElement.tagName(), QString("title")); - QCOMPARE(bookTitleElement.prefix(), QString("book")); - QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS)); - QCOMPARE(bookTitleElement.localName(), QString("title")); - - // another way, find it using namedItemNS() - KoXmlElement book2TitleElement; - book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title"); - //book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement(); - QCOMPARE(book2TitleElement == bookTitleElement, true); - QCOMPARE(book2TitleElement.isNull(), false); - QCOMPARE(book2TitleElement.isElement(), true); - QCOMPARE(book2TitleElement.tagName(), QString("title")); - - // - bookAuthorElement = bookTitleElement.nextSibling().toElement(); - QCOMPARE(bookAuthorElement.isNull(), false); - QCOMPARE(bookAuthorElement.isElement(), true); - QCOMPARE(bookAuthorElement.tagName(), QString("author")); - QCOMPARE(bookAuthorElement.prefix(), QString("book")); - QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS)); - QCOMPARE(bookAuthorElement.localName(), QString("author")); - - // another way, find it using namedItemNS() - KoXmlElement book2AuthorElement; - book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author"); - //book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement(); - QCOMPARE(book2AuthorElement == bookAuthorElement, true); - QCOMPARE(book2AuthorElement.isNull(), false); - QCOMPARE(book2AuthorElement.isElement(), true); - QCOMPARE(book2AuthorElement.tagName(), QString("author")); - - // attributes in - // Note: with namespace processing, attribute's prefix is taken out and - // hence "fnord:title" will simply override "title" - // and searching attribute with prefix will give no result - QCOMPARE(bookAuthorElement.hasAttribute("title"), true); - QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttribute("name"), true); - QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess")); - QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true); - QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); - - // attributes in , with NS family of functions - // those without prefix are not accessible at all, because they do not belong - // to any namespace at all. - // Note: default namespace does not apply to attribute names! - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true); - - QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess")); - QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString()); - QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess")); - - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false); - - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false); - - QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true); - QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true); - QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true); -} - -// mostly similar to testNamespace above, but parse from a QString -void TestXmlReaderWithoutSpaces::testParseQString() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QString xmlText; - xmlText += ""; - xmlText += ""; - xmlText += "Practical XML"; - xmlText += ""; - xmlText += ""; - xmlText += "A Namespace Called fnord"; - xmlText += ""; - xmlText += ""; - xmlText += ""; - - KoXmlDocument doc(false); - KoXmlElement rootElement; - KoXmlElement bookElement; - KoXmlElement bookTitleElement; - KoXmlElement bookAuthorElement; - - QCOMPARE(doc.setContent(xmlText, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* defaultNS = "http://trolltech.com/fnord/"; - const char* bookNS = "http://trolltech.com/fnord/book/"; - const char* fnordNS = "http://trolltech.com/fnord/"; - - // - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.tagName(), QString("document")); - QCOMPARE(rootElement.prefix().isEmpty(), true); - QCOMPARE(rootElement.namespaceURI(), QString(defaultNS)); - QCOMPARE(rootElement.localName(), QString("document")); - - // - bookElement = rootElement.firstChild().toElement(); - QCOMPARE(bookElement.isNull(), false); - QCOMPARE(bookElement.isElement(), true); - QCOMPARE(bookElement.tagName(), QString("book")); - QCOMPARE(bookElement.prefix().isEmpty(), true); - QCOMPARE(bookElement.namespaceURI(), QString(defaultNS)); - QCOMPARE(bookElement.localName(), QString("book")); - - // - bookTitleElement = bookElement.firstChild().toElement(); - QCOMPARE(bookTitleElement.isNull(), false); - QCOMPARE(bookTitleElement.isElement(), true); - QCOMPARE(bookTitleElement.tagName(), QString("title")); - QCOMPARE(bookTitleElement.prefix(), QString("book")); - QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS)); - QCOMPARE(bookTitleElement.localName(), QString("title")); - - // another way, find it using namedItemNS() - KoXmlElement book2TitleElement; - book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title"); - //book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement(); - QCOMPARE(book2TitleElement == bookTitleElement, true); - QCOMPARE(book2TitleElement.isNull(), false); - QCOMPARE(book2TitleElement.isElement(), true); - QCOMPARE(book2TitleElement.tagName(), QString("title")); - - // - bookAuthorElement = bookTitleElement.nextSibling().toElement(); - QCOMPARE(bookAuthorElement.isNull(), false); - QCOMPARE(bookAuthorElement.isElement(), true); - QCOMPARE(bookAuthorElement.tagName(), QString("author")); - QCOMPARE(bookAuthorElement.prefix(), QString("book")); - QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS)); - QCOMPARE(bookAuthorElement.localName(), QString("author")); - - // another way, find it using namedItemNS() - KoXmlElement book2AuthorElement; - book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author"); - //book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement(); - QCOMPARE(book2AuthorElement == bookAuthorElement, true); - QCOMPARE(book2AuthorElement.isNull(), false); - QCOMPARE(book2AuthorElement.isElement(), true); - QCOMPARE(book2AuthorElement.tagName(), QString("author")); - - // attributes in - // Note: with namespace processing, attribute's prefix is taken out and - // hence "fnord:title" will simply override "title" - // and searching attribute with prefix will give no result - QCOMPARE(bookAuthorElement.hasAttribute("title"), true); - QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttribute("name"), true); - QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess")); - QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true); - QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); - - // attributes in , with NS family of functions - // those without prefix are not accessible at all, because they do not belong - // to any namespace at all. - // Note: default namespace does not apply to attribute names! - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true); - - QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess")); - QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString()); - QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess")); - - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false); - - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false); - - QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true); - QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true); - QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true); -} - -void TestXmlReaderWithoutSpaces::testUnload() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - KoXmlElement earthElement; - earthElement = doc.documentElement().toElement(); - QCOMPARE(earthElement.isNull(), false); - QCOMPARE(earthElement.isElement(), true); - QCOMPARE(earthElement.parentNode().isNull(), false); - QCOMPARE(earthElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(earthElement), 2); - QCOMPARE(earthElement.tagName(), QString("earth")); - QCOMPARE(earthElement.prefix().isNull(), true); - - // this ensures that all child nodes of are loaded - earthElement.firstChild(); - - // explicitly unload all child nodes of - KoXml::unload(earthElement); - - // we should get the correct first child - KoXmlElement continentsElement = earthElement.firstChild().toElement(); - QCOMPARE(continentsElement.nodeName(), QString("continents")); - QCOMPARE(continentsElement.isNull(), false); - QCOMPARE(continentsElement.isElement(), true); - QCOMPARE(continentsElement.isText(), false); - QCOMPARE(continentsElement.isDocument(), false); - QCOMPARE(continentsElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(continentsElement), 6); - - // let us unload everything again - KoXml::unload(earthElement); - - // we should get the correct last child - KoXmlElement oceansElement = earthElement.lastChild().toElement(); - QCOMPARE(oceansElement.nodeName(), QString("oceans")); - QCOMPARE(oceansElement.isNull(), false); - QCOMPARE(oceansElement.isElement(), true); - QCOMPARE(oceansElement.isText(), false); - QCOMPARE(oceansElement.isDocument(), false); - QCOMPARE(oceansElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(continentsElement), 6); -} - -void TestXmlReaderWithoutSpaces::testSimpleXML() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 5); - QCOMPARE(rootElement.tagName(), QString("solarsystem")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // node - KoXmlNode firstPlanetNode; - firstPlanetNode = rootElement.firstChild(); - QCOMPARE(firstPlanetNode.isNull(), false); - QCOMPARE(firstPlanetNode.isElement(), true); - QCOMPARE(firstPlanetNode.nextSibling().isNull(), false); - QCOMPARE(firstPlanetNode.previousSibling().isNull(), true); - QCOMPARE(firstPlanetNode.parentNode().isNull(), false); - QCOMPARE(firstPlanetNode.parentNode() == rootElement, true); - QCOMPARE(firstPlanetNode.parentNode() != rootElement, false); - QCOMPARE(firstPlanetNode.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0); - QCOMPARE(firstPlanetNode.firstChild().isNull(), true); - QCOMPARE(firstPlanetNode.lastChild().isNull(), true); - - // element - KoXmlElement firstPlanetElement; - firstPlanetElement = firstPlanetNode.toElement(); - QCOMPARE(firstPlanetElement.isNull(), false); - QCOMPARE(firstPlanetElement.isElement(), true); - QCOMPARE(firstPlanetElement.parentNode().isNull(), false); - QCOMPARE(firstPlanetElement.parentNode() == rootElement, true); - QCOMPARE(firstPlanetElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0); - QCOMPARE(firstPlanetElement.firstChild().isNull(), true); - QCOMPARE(firstPlanetElement.lastChild().isNull(), true); - QCOMPARE(firstPlanetElement.tagName(), QString("mercurius")); - QCOMPARE(firstPlanetElement.prefix().isNull(), true); - - // node - KoXmlNode secondPlanetNode; - secondPlanetNode = firstPlanetNode.nextSibling(); - QCOMPARE(secondPlanetNode.isNull(), false); - QCOMPARE(secondPlanetNode.isElement(), true); - QCOMPARE(secondPlanetNode.nextSibling().isNull(), false); - QCOMPARE(secondPlanetNode.previousSibling().isNull(), false); - QCOMPARE(secondPlanetNode.previousSibling() == firstPlanetNode, true); - QCOMPARE(secondPlanetNode.previousSibling() == firstPlanetElement, true); - QCOMPARE(secondPlanetNode.parentNode().isNull(), false); - QCOMPARE(secondPlanetNode.parentNode() == rootElement, true); - QCOMPARE(secondPlanetNode.parentNode() != rootElement, false); - QCOMPARE(secondPlanetNode.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0); - QCOMPARE(secondPlanetNode.firstChild().isNull(), true); - QCOMPARE(secondPlanetNode.lastChild().isNull(), true); - - // element - KoXmlElement secondPlanetElement; - secondPlanetElement = secondPlanetNode.toElement(); - QCOMPARE(secondPlanetElement.isNull(), false); - QCOMPARE(secondPlanetElement.isElement(), true); - QCOMPARE(secondPlanetElement.nextSibling().isNull(), false); - QCOMPARE(secondPlanetElement.previousSibling().isNull(), false); - QCOMPARE(secondPlanetElement.previousSibling() == firstPlanetNode, true); - QCOMPARE(secondPlanetElement.previousSibling() == firstPlanetElement, true); - QCOMPARE(secondPlanetElement.parentNode().isNull(), false); - QCOMPARE(secondPlanetElement.parentNode() == rootElement, true); - QCOMPARE(secondPlanetElement.parentNode() != rootElement, false); - QCOMPARE(secondPlanetElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0); - QCOMPARE(secondPlanetElement.firstChild().isNull(), true); - QCOMPARE(secondPlanetElement.lastChild().isNull(), true); - QCOMPARE(secondPlanetElement.tagName(), QString("venus")); - QCOMPARE(secondPlanetElement.prefix().isNull(), true); -} - -void TestXmlReaderWithoutSpaces::testRootError() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - // multiple root nodes are not valid ! - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false); - QCOMPARE(errorMsg.isEmpty(), false); - QCOMPARE(errorMsg, QString("Extra content at end of document.")); - QCOMPARE(errorLine, 1); - QCOMPARE(errorColumn, 21); -} - -void TestXmlReaderWithoutSpaces::testMismatchedTag() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false); - QCOMPARE(errorMsg.isEmpty(), false); - QCOMPARE(errorMsg, QString("Opening and ending tag mismatch.")); - QCOMPARE(errorLine, 1); - QCOMPARE(errorColumn, 11); -} - -static void dumpNodes(const KoXmlNode &node, int level=0) -{ - QString indent = QString("%1").arg("", level*3); - if (node.isNull()) { - qDebug()<"; - xmlstream << ""; - xmlstream << ""; - xmlstream << "

The best place

"; - xmlstream << ""; - xmlstream << "
"; - xmlstream << ""; - xmlstream << ""; - xmlstream << "
"; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - dumpNodes(doc); - // - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 5); - QCOMPARE(rootElement.tagName(), QString("solarsystem")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // now test converting KoXmlDocument to QDomDocument - QDomDocument universeDoc = KoXml::asQDomDocument(doc); - - // - QDomElement solarSystemElement = universeDoc.documentElement(); - QCOMPARE(solarSystemElement.isNull(), false); - QCOMPARE(solarSystemElement.isElement(), true); - QCOMPARE(solarSystemElement.parentNode().isNull(), false); - QCOMPARE(solarSystemElement.hasChildNodes(), true); - QCOMPARE(solarSystemElement.tagName(), QString("solarsystem")); - QCOMPARE(solarSystemElement.prefix().isNull(), true); - - // - QDomElement earthElement = solarSystemElement.namedItem("earth").toElement(); - QCOMPARE(earthElement.isNull(), false); - QCOMPARE(earthElement.isElement(), true); - QCOMPARE(earthElement.parentNode().isNull(), false); - QCOMPARE(earthElement.hasAttribute("habitable"), true); - QCOMPARE(earthElement.hasChildNodes(), true); - QCOMPARE(earthElement.tagName(), QString("earth")); - QCOMPARE(earthElement.prefix().isNull(), true); - - //

in - QDomNode placeNode = earthElement.firstChild(); - qDebug()<<"placeNode"<"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "

The best place

"; - xmlstream << ""; - xmlstream << "
"; - xmlstream << ""; - xmlstream << ""; - xmlstream << "
"; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 1); - QCOMPARE(rootElement.tagName(), QString("universe")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // now test converting KoXmlElement to QDomElement - QDomDocument solarDoc; - KoXml::asQDomElement(solarDoc, rootElement.firstChild().toElement()); - - // - QDomElement solarSystemElement = solarDoc.documentElement(); - QCOMPARE(solarSystemElement.isNull(), false); - QCOMPARE(solarSystemElement.isElement(), true); - QCOMPARE(solarSystemElement.parentNode().isNull(), false); - QCOMPARE(solarSystemElement.hasChildNodes(), true); - QCOMPARE(solarSystemElement.tagName(), QString("solarsystem")); - QCOMPARE(solarSystemElement.prefix().isNull(), true); - - // - QDomElement earthElement = solarSystemElement.namedItem("earth").toElement(); - QCOMPARE(earthElement.isNull(), false); - QCOMPARE(earthElement.isElement(), true); - QCOMPARE(earthElement.parentNode().isNull(), false); - QCOMPARE(earthElement.hasAttribute("habitable"), true); - QCOMPARE(earthElement.hasChildNodes(), true); - QCOMPARE(earthElement.tagName(), QString("earth")); - QCOMPARE(earthElement.prefix().isNull(), true); - - //

in - QDomNode placeNode = earthElement.firstChild(); - QCOMPARE(placeNode.isNull(), false); - QCOMPARE(placeNode.isElement(), true); - QCOMPARE(placeNode.toElement().text(), QString("The best place")); - QCOMPARE(placeNode.nextSibling().isNull(), false); - QCOMPARE(placeNode.previousSibling().isNull(), true); - QCOMPARE(placeNode.parentNode().isNull(), false); - QCOMPARE(placeNode.parentNode() == earthElement, true); - QCOMPARE(placeNode.hasChildNodes(), true); - QCOMPARE(placeNode.childNodes().count(), 1); - - //printf("Result:\n%s\n\n", qPrintable(universeDoc.toString())); -} - - -void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentText() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // content.xml from a simple OpenDocument text - // it has only paragraph "Hello, world!" - // automatic styles, declarations and unnecessary namespaces are omitted. - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "Hello, world!"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.parentNode().isNull(), false); - QCOMPARE(contentElement.parentNode().toDocument() == doc, true); - QCOMPARE(KoXml::childNodesCount(contentElement), 2); - QCOMPARE(contentElement.firstChild().isNull(), false); - QCOMPARE(contentElement.lastChild().isNull(), false); - QCOMPARE(contentElement.previousSibling().isNull(), false); - QCOMPARE(contentElement.nextSibling().isNull(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true); - QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0")); - - // - KoXmlElement stylesElement; - stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles"); - QCOMPARE(stylesElement.isNull(), false); - QCOMPARE(stylesElement.isElement(), true); - QCOMPARE(stylesElement.parentNode().isNull(), false); - QCOMPARE(stylesElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(stylesElement), 0); - QCOMPARE(stylesElement.firstChild().isNull(), true); - QCOMPARE(stylesElement.lastChild().isNull(), true); - QCOMPARE(stylesElement.previousSibling().isNull(), true); - QCOMPARE(stylesElement.nextSibling().isNull(), false); - QCOMPARE(stylesElement.localName(), QString("automatic-styles")); - - // also same , but without namedItemNS - KoXmlNode styles2Element; - styles2Element = contentElement.firstChild().toElement(); - QCOMPARE(styles2Element.isNull(), false); - QCOMPARE(styles2Element.isElement(), true); - QCOMPARE(styles2Element.parentNode().isNull(), false); - QCOMPARE(styles2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(styles2Element), 0); - QCOMPARE(styles2Element.firstChild().isNull(), true); - QCOMPARE(styles2Element.lastChild().isNull(), true); - QCOMPARE(styles2Element.previousSibling().isNull(), true); - QCOMPARE(styles2Element.nextSibling().isNull(), false); - QCOMPARE(styles2Element.localName(), QString("automatic-styles")); - - // - KoXmlElement bodyElement; - bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body"); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.parentNode().isNull(), false); - QCOMPARE(bodyElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(bodyElement), 1); - QCOMPARE(bodyElement.firstChild().isNull(), false); - QCOMPARE(bodyElement.lastChild().isNull(), false); - QCOMPARE(bodyElement.previousSibling().isNull(), false); - QCOMPARE(bodyElement.nextSibling().isNull(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - // also same , but without namedItemNS - KoXmlElement body2Element; - body2Element = stylesElement.nextSibling().toElement(); - QCOMPARE(body2Element.isNull(), false); - QCOMPARE(body2Element.isElement(), true); - QCOMPARE(body2Element.parentNode().isNull(), false); - QCOMPARE(body2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(body2Element), 1); - QCOMPARE(body2Element.firstChild().isNull(), false); - QCOMPARE(body2Element.lastChild().isNull(), false); - QCOMPARE(body2Element.previousSibling().isNull(), false); - QCOMPARE(body2Element.nextSibling().isNull(), true); - QCOMPARE(body2Element.localName(), QString("body")); - - // - KoXmlElement textElement; - textElement = KoXml::namedItemNS(bodyElement, officeNS, "text"); - QCOMPARE(textElement.isNull(), false); - QCOMPARE(textElement.isElement(), true); - QCOMPARE(textElement.parentNode().isNull(), false); - QCOMPARE(textElement.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(textElement), 1); - QCOMPARE(textElement.firstChild().isNull(), false); - QCOMPARE(textElement.lastChild().isNull(), false); - QCOMPARE(textElement.previousSibling().isNull(), true); - QCOMPARE(textElement.nextSibling().isNull(), true); - QCOMPARE(textElement.localName(), QString("text")); - - // the same , but without namedItemNS - KoXmlElement text2Element; - text2Element = bodyElement.firstChild().toElement(); - QCOMPARE(text2Element.isNull(), false); - QCOMPARE(text2Element.isElement(), true); - QCOMPARE(text2Element.parentNode().isNull(), false); - QCOMPARE(text2Element.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(text2Element), 1); - QCOMPARE(text2Element.firstChild().isNull(), false); - QCOMPARE(text2Element.lastChild().isNull(), false); - QCOMPARE(text2Element.previousSibling().isNull(), true); - QCOMPARE(text2Element.nextSibling().isNull(), true); - QCOMPARE(text2Element.localName(), QString("text")); - - // - KoXmlElement parElement; - parElement = textElement.firstChild().toElement(); - QCOMPARE(parElement.isNull(), false); - QCOMPARE(parElement.isElement(), true); - QCOMPARE(parElement.parentNode().isNull(), false); - QCOMPARE(parElement.parentNode() == textElement, true); - QCOMPARE(KoXml::childNodesCount(parElement), 1); - QCOMPARE(parElement.firstChild().isNull(), false); - QCOMPARE(parElement.lastChild().isNull(), false); - QCOMPARE(parElement.previousSibling().isNull(), true); - QCOMPARE(parElement.nextSibling().isNull(), true); - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.text(), QString("Hello, world!")); - QCOMPARE(parElement.attributeNS(QString(textNS), "style-name", ""), QString("Standard")); -} - -void TestXmlReaderWithoutSpaces::testWhitespace() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // content.xml for testing paragraphs with whitespace - /* The list of elements for which whitespace should be preserved can be - obtained from the Relax NG schema with these commands: - - cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \ - -N s="http://relaxng.org/ns/structure/1.0" -t -m '//s:text' \ - -v '../@name' -n |grep : - cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \ - -N s="http://relaxng.org/ns/structure/1.0" \ - -t -m "//s:ref[@name='paragraph-content']" -v '../../@name' -n |grep : - */ - - xmlstream << ""; - xmlstream << ""; - xmlstream << " "; - xmlstream << " "; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - KoXmlElement p1; - p1 = doc.documentElement().firstChild().toElement(); - QCOMPARE(p1.isNull(), false); - QCOMPARE(p1.isElement(), true); - QCOMPARE(KoXml::childNodesCount(p1), 1); - - KoXmlElement p2; - p2 = p1.nextSibling().toElement(); - QCOMPARE(p2.isNull(), false); - QCOMPARE(p2.isElement(), true); - QCOMPARE(KoXml::childNodesCount(p2), 3); -} - -void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentSpreadsheet() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // content.xml from a simple OpenDocument spreadsheet - // the document has three worksheets, the last two are empty. - // on the first sheet, cell A1 contains the text "Hello, world". - // automatic styles, font declarations and unnecessary namespaces are omitted. - - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "Hello, world"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; - QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.parentNode().isNull(), false); - QCOMPARE(contentElement.parentNode().toDocument() == doc, true); - QCOMPARE(KoXml::childNodesCount(contentElement), 1); - QCOMPARE(contentElement.firstChild().isNull(), false); - QCOMPARE(contentElement.lastChild().isNull(), false); - QCOMPARE(contentElement.previousSibling().isNull(), false); - QCOMPARE(contentElement.nextSibling().isNull(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - - // - KoXmlElement bodyElement; - bodyElement = contentElement.firstChild().toElement(); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.parentNode().isNull(), false); - QCOMPARE(bodyElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(bodyElement), 1); - QCOMPARE(bodyElement.firstChild().isNull(), false); - QCOMPARE(bodyElement.lastChild().isNull(), false); - QCOMPARE(bodyElement.previousSibling().isNull(), true); - QCOMPARE(bodyElement.nextSibling().isNull(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - // - KoXmlElement spreadsheetElement; - spreadsheetElement = bodyElement.firstChild().toElement(); - QCOMPARE(spreadsheetElement.isNull(), false); - QCOMPARE(spreadsheetElement.isElement(), true); - QCOMPARE(spreadsheetElement.parentNode().isNull(), false); - QCOMPARE(spreadsheetElement.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(spreadsheetElement), 3); - QCOMPARE(spreadsheetElement.firstChild().isNull(), false); - QCOMPARE(spreadsheetElement.lastChild().isNull(), false); - QCOMPARE(spreadsheetElement.previousSibling().isNull(), true); - QCOMPARE(spreadsheetElement.nextSibling().isNull(), true); - QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); - - // for Sheet1 - KoXmlElement sheet1Element; - sheet1Element = spreadsheetElement.firstChild().toElement(); - QCOMPARE(sheet1Element.isNull(), false); - QCOMPARE(sheet1Element.isElement(), true); - QCOMPARE(sheet1Element.parentNode().isNull(), false); - QCOMPARE(sheet1Element.parentNode() == spreadsheetElement, true); - QCOMPARE(KoXml::childNodesCount(sheet1Element), 2); - QCOMPARE(sheet1Element.firstChild().isNull(), false); - QCOMPARE(sheet1Element.lastChild().isNull(), false); - QCOMPARE(sheet1Element.previousSibling().isNull(), true); - QCOMPARE(sheet1Element.nextSibling().isNull(), false); - QCOMPARE(sheet1Element.tagName(), QString("table")); - QCOMPARE(sheet1Element.hasAttributeNS(tableNS, "name"), true); - QCOMPARE(sheet1Element.attributeNS(tableNS, "name", ""), QString("Sheet1")); - QCOMPARE(sheet1Element.attributeNS(tableNS, "style-name", ""), QString("ta1")); - QCOMPARE(sheet1Element.attributeNS(tableNS, "print", ""), QString("false")); - - // KoXml::load( sheet1Element, 100 ); - - // - KoXmlElement columnElement; - columnElement = sheet1Element.firstChild().toElement(); - QCOMPARE(columnElement.isNull(), false); - QCOMPARE(columnElement.isElement(), true); - QCOMPARE(columnElement.parentNode().isNull(), false); - QCOMPARE(columnElement.parentNode() == sheet1Element, true); - QCOMPARE(KoXml::childNodesCount(columnElement), 0); - QCOMPARE(columnElement.firstChild().isNull(), true); - QCOMPARE(columnElement.lastChild().isNull(), true); - QCOMPARE(columnElement.previousSibling().isNull(), true); - QCOMPARE(columnElement.nextSibling().isNull(), false); - QCOMPARE(columnElement.tagName(), QString("table-column")); - QCOMPARE(columnElement.attributeNS(tableNS, "style-name", ""), QString("co1")); - QCOMPARE(columnElement.attributeNS(tableNS, "default-cell-style-name", ""), QString("Default")); - - // - KoXmlElement rowElement; - rowElement = columnElement.nextSibling().toElement(); - QCOMPARE(rowElement.isNull(), false); - QCOMPARE(rowElement.isElement(), true); - QCOMPARE(rowElement.parentNode().isNull(), false); - QCOMPARE(rowElement.parentNode() == sheet1Element, true); - QCOMPARE(KoXml::childNodesCount(rowElement), 1); - QCOMPARE(rowElement.firstChild().isNull(), false); - QCOMPARE(rowElement.lastChild().isNull(), false); - QCOMPARE(rowElement.previousSibling().isNull(), false); - QCOMPARE(rowElement.nextSibling().isNull(), true); - QCOMPARE(rowElement.tagName(), QString("table-row")); - QCOMPARE(rowElement.attributeNS(tableNS, "style-name", ""), QString("ro1")); - - // - KoXmlElement cellElement; - cellElement = rowElement.firstChild().toElement(); - QCOMPARE(cellElement.isNull(), false); - QCOMPARE(cellElement.isElement(), true); - QCOMPARE(cellElement.parentNode().isNull(), false); - QCOMPARE(cellElement.parentNode() == rowElement, true); - QCOMPARE(KoXml::childNodesCount(cellElement), 1); - QCOMPARE(cellElement.firstChild().isNull(), false); - QCOMPARE(cellElement.lastChild().isNull(), false); - QCOMPARE(cellElement.previousSibling().isNull(), true); - QCOMPARE(cellElement.nextSibling().isNull(), true); - QCOMPARE(cellElement.tagName(), QString("table-cell")); - QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string")); - - // - KoXmlElement parElement; - parElement = cellElement.firstChild().toElement(); - QCOMPARE(parElement.isNull(), false); - QCOMPARE(parElement.isElement(), true); - QCOMPARE(parElement.parentNode().isNull(), false); - QCOMPARE(parElement.parentNode() == cellElement, true); - QCOMPARE(KoXml::childNodesCount(parElement), 1); - QCOMPARE(parElement.firstChild().isNull(), false); - QCOMPARE(parElement.lastChild().isNull(), false); - QCOMPARE(parElement.previousSibling().isNull(), true); - QCOMPARE(parElement.nextSibling().isNull(), true); - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.text(), QString("Hello, world")); - - // for Sheet2 - KoXmlElement sheet2Element; - sheet2Element = sheet1Element.nextSibling().toElement(); - QCOMPARE(sheet2Element.isNull(), false); - QCOMPARE(sheet2Element.isElement(), true); - QCOMPARE(sheet2Element.parentNode().isNull(), false); - QCOMPARE(sheet2Element.parentNode() == spreadsheetElement, true); - QCOMPARE(KoXml::childNodesCount(sheet2Element), 2); - QCOMPARE(sheet2Element.firstChild().isNull(), false); - QCOMPARE(sheet2Element.lastChild().isNull(), false); - QCOMPARE(sheet2Element.previousSibling().isNull(), false); - QCOMPARE(sheet2Element.nextSibling().isNull(), false); - QCOMPARE(sheet2Element.tagName(), QString("table")); - - // for Sheet3 - KoXmlElement sheet3Element; - sheet3Element = sheet2Element.nextSibling().toElement(); - QCOMPARE(sheet3Element.isNull(), false); - QCOMPARE(sheet3Element.isElement(), true); - QCOMPARE(sheet3Element.parentNode().isNull(), false); - QCOMPARE(sheet3Element.parentNode() == spreadsheetElement, true); - QCOMPARE(KoXml::childNodesCount(sheet3Element), 2); - QCOMPARE(sheet3Element.firstChild().isNull(), false); - QCOMPARE(sheet3Element.lastChild().isNull(), false); - QCOMPARE(sheet3Element.previousSibling().isNull(), false); - QCOMPARE(sheet3Element.nextSibling().isNull(), true); - QCOMPARE(sheet3Element.tagName(), QString("table")); -} - -void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentPresentation() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // content.xml from a simple OpenDocument presentation - // styles, declarations and unnecessary namespaces are omitted - // the first page is "Title" and has two text boxes - // the second page is - - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "Foobar"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "Foo"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - const char* drawNS = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"; - const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - const char* presentationNS = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"; - const char* svgNS = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.parentNode().isNull(), false); - QCOMPARE(contentElement.parentNode().toDocument() == doc, true); - QCOMPARE(KoXml::childNodesCount(contentElement), 3); - QCOMPARE(contentElement.firstChild().isNull(), false); - QCOMPARE(contentElement.lastChild().isNull(), false); - QCOMPARE(contentElement.previousSibling().isNull(), false); - QCOMPARE(contentElement.nextSibling().isNull(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true); - QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0")); - - // - KoXmlElement scriptsElement; - scriptsElement = KoXml::namedItemNS(contentElement, officeNS, "scripts"); - QCOMPARE(scriptsElement.isNull(), false); - QCOMPARE(scriptsElement.isElement(), true); - QCOMPARE(scriptsElement.parentNode().isNull(), false); - QCOMPARE(scriptsElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(scriptsElement), 0); - QCOMPARE(scriptsElement.firstChild().isNull(), true); - QCOMPARE(scriptsElement.lastChild().isNull(), true); - QCOMPARE(scriptsElement.previousSibling().isNull(), true); - QCOMPARE(scriptsElement.nextSibling().isNull(), false); - QCOMPARE(scriptsElement.localName(), QString("scripts")); - - // - KoXmlElement stylesElement; - stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles"); - QCOMPARE(stylesElement.isNull(), false); - QCOMPARE(stylesElement.isElement(), true); - QCOMPARE(stylesElement.parentNode().isNull(), false); - QCOMPARE(stylesElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(stylesElement), 0); - QCOMPARE(stylesElement.firstChild().isNull(), true); - QCOMPARE(stylesElement.lastChild().isNull(), true); - QCOMPARE(stylesElement.previousSibling().isNull(), false); - QCOMPARE(stylesElement.nextSibling().isNull(), false); - QCOMPARE(stylesElement.localName(), QString("automatic-styles")); - - // also same , but without namedItemNS - KoXmlNode styles2Element; - styles2Element = scriptsElement.nextSibling().toElement(); - QCOMPARE(styles2Element.isNull(), false); - QCOMPARE(styles2Element.isElement(), true); - QCOMPARE(styles2Element.parentNode().isNull(), false); - QCOMPARE(styles2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(styles2Element), 0); - QCOMPARE(styles2Element.firstChild().isNull(), true); - QCOMPARE(styles2Element.lastChild().isNull(), true); - QCOMPARE(styles2Element.previousSibling().isNull(), false); - QCOMPARE(styles2Element.nextSibling().isNull(), false); - QCOMPARE(styles2Element.localName(), QString("automatic-styles")); - - // - KoXmlElement bodyElement; - bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body"); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.parentNode().isNull(), false); - QCOMPARE(bodyElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(bodyElement), 1); - QCOMPARE(bodyElement.firstChild().isNull(), false); - QCOMPARE(bodyElement.lastChild().isNull(), false); - QCOMPARE(bodyElement.previousSibling().isNull(), false); - QCOMPARE(bodyElement.nextSibling().isNull(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - // also same , but without namedItemNS - KoXmlElement body2Element; - body2Element = stylesElement.nextSibling().toElement(); - QCOMPARE(body2Element.isNull(), false); - QCOMPARE(body2Element.isElement(), true); - QCOMPARE(body2Element.parentNode().isNull(), false); - QCOMPARE(body2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(body2Element), 1); - QCOMPARE(body2Element.firstChild().isNull(), false); - QCOMPARE(body2Element.lastChild().isNull(), false); - QCOMPARE(body2Element.previousSibling().isNull(), false); - QCOMPARE(body2Element.nextSibling().isNull(), true); - QCOMPARE(body2Element.localName(), QString("body")); - - // - KoXmlElement presentationElement; - presentationElement = KoXml::namedItemNS(bodyElement, officeNS, "presentation"); - QCOMPARE(presentationElement.isNull(), false); - QCOMPARE(presentationElement.isElement(), true); - QCOMPARE(presentationElement.parentNode().isNull(), false); - QCOMPARE(presentationElement.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(presentationElement), 2); - QCOMPARE(presentationElement.firstChild().isNull(), false); - QCOMPARE(presentationElement.lastChild().isNull(), false); - QCOMPARE(presentationElement.previousSibling().isNull(), true); - QCOMPARE(presentationElement.nextSibling().isNull(), true); - QCOMPARE(presentationElement.localName(), QString("presentation")); - - // the same , but without namedItemNS - KoXmlElement presentation2Element; - presentation2Element = bodyElement.firstChild().toElement(); - QCOMPARE(presentation2Element.isNull(), false); - QCOMPARE(presentation2Element.isElement(), true); - QCOMPARE(presentation2Element.parentNode().isNull(), false); - QCOMPARE(presentation2Element.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(presentation2Element), 2); - QCOMPARE(presentation2Element.firstChild().isNull(), false); - QCOMPARE(presentation2Element.lastChild().isNull(), false); - QCOMPARE(presentation2Element.previousSibling().isNull(), true); - QCOMPARE(presentation2Element.nextSibling().isNull(), true); - QCOMPARE(presentation2Element.localName(), QString("presentation")); - - // for "Title" - KoXmlElement titlePageElement; - titlePageElement = presentationElement.firstChild().toElement(); - QCOMPARE(titlePageElement.isNull(), false); - QCOMPARE(titlePageElement.isElement(), true); - QCOMPARE(titlePageElement.parentNode().isNull(), false); - QCOMPARE(titlePageElement.parentNode() == presentationElement, true); - QCOMPARE(KoXml::childNodesCount(titlePageElement), 3); - QCOMPARE(titlePageElement.firstChild().isNull(), false); - QCOMPARE(titlePageElement.lastChild().isNull(), false); - QCOMPARE(titlePageElement.previousSibling().isNull(), true); - QCOMPARE(titlePageElement.nextSibling().isNull(), false); - QCOMPARE(titlePageElement.localName(), QString("page")); - QCOMPARE(titlePageElement.attributeNS(drawNS, "name", ""), QString("Title")); - QCOMPARE(titlePageElement.attributeNS(drawNS, "style-name", ""), QString("dp1")); - QCOMPARE(titlePageElement.attributeNS(drawNS, "master-page-name", ""), QString("lyt-cool")); - QCOMPARE(titlePageElement.attributeNS(presentationNS, - "presentation-page-layout-name", ""), QString("AL1T0")); - - // for the title frame - KoXmlElement titleFrameElement; - titleFrameElement = titlePageElement.firstChild().toElement(); - QCOMPARE(titleFrameElement.isNull(), false); - QCOMPARE(titleFrameElement.isElement(), true); - QCOMPARE(titleFrameElement.parentNode().isNull(), false); - QCOMPARE(titleFrameElement.parentNode() == titlePageElement, true); - QCOMPARE(KoXml::childNodesCount(titleFrameElement), 1); - QCOMPARE(titleFrameElement.firstChild().isNull(), false); - QCOMPARE(titleFrameElement.lastChild().isNull(), false); - QCOMPARE(titleFrameElement.previousSibling().isNull(), true); - QCOMPARE(titleFrameElement.nextSibling().isNull(), false); - QCOMPARE(titleFrameElement.localName(), QString("frame")); - QCOMPARE(titleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr1")); - QCOMPARE(titleFrameElement.attributeNS(presentationNS, "class", ""), QString("title")); - QCOMPARE(titleFrameElement.attributeNS(presentationNS, "user-transformed", ""), QString("true")); - QCOMPARE(titleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P2")); - QCOMPARE(titleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout")); - QCOMPARE(titleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm")); - QCOMPARE(titleFrameElement.attributeNS(svgNS, "height", ""), QString("3.508cm")); - QCOMPARE(titleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm")); - QCOMPARE(titleFrameElement.attributeNS(svgNS, "y", ""), QString("1.543cm")); - - // of the title frame - KoXmlElement titleBoxElement; - titleBoxElement = titleFrameElement.firstChild().toElement(); - QCOMPARE(titleBoxElement.isNull(), false); - QCOMPARE(titleBoxElement.isElement(), true); - QCOMPARE(titleBoxElement.parentNode().isNull(), false); - QCOMPARE(titleBoxElement.parentNode() == titleFrameElement, true); - QCOMPARE(KoXml::childNodesCount(titleBoxElement), 1); - QCOMPARE(titleBoxElement.firstChild().isNull(), false); - QCOMPARE(titleBoxElement.lastChild().isNull(), false); - QCOMPARE(titleBoxElement.previousSibling().isNull(), true); - QCOMPARE(titleBoxElement.nextSibling().isNull(), true); - QCOMPARE(titleBoxElement.localName(), QString("text-box")); - - // for the title text-box - KoXmlElement titleParElement; - titleParElement = titleBoxElement.firstChild().toElement(); - QCOMPARE(titleParElement.isNull(), false); - QCOMPARE(titleParElement.isElement(), true); - QCOMPARE(titleParElement.parentNode().isNull(), false); - QCOMPARE(titleParElement.parentNode() == titleBoxElement, true); - QCOMPARE(KoXml::childNodesCount(titleParElement), 1); - QCOMPARE(titleParElement.firstChild().isNull(), false); - QCOMPARE(titleParElement.lastChild().isNull(), false); - QCOMPARE(titleParElement.previousSibling().isNull(), true); - QCOMPARE(titleParElement.nextSibling().isNull(), true); - QCOMPARE(titleParElement.localName(), QString("p")); - QCOMPARE(titleParElement.attributeNS(textNS, "style-name", ""), QString("P1")); - QCOMPARE(titleParElement.text(), QString("Foobar")); - - // for the subtitle frame - KoXmlElement subtitleFrameElement; - subtitleFrameElement = titleFrameElement.nextSibling().toElement(); - QCOMPARE(subtitleFrameElement.isNull(), false); - QCOMPARE(subtitleFrameElement.isElement(), true); - QCOMPARE(subtitleFrameElement.parentNode().isNull(), false); - QCOMPARE(subtitleFrameElement.parentNode() == titlePageElement, true); - QCOMPARE(KoXml::childNodesCount(subtitleFrameElement), 1); - QCOMPARE(subtitleFrameElement.firstChild().isNull(), false); - QCOMPARE(subtitleFrameElement.lastChild().isNull(), false); - QCOMPARE(subtitleFrameElement.previousSibling().isNull(), false); - QCOMPARE(subtitleFrameElement.nextSibling().isNull(), false); - QCOMPARE(subtitleFrameElement.localName(), QString("frame")); - QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr2")); - QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "class", ""), QString("subtitle")); - QCOMPARE(subtitleFrameElement.hasAttributeNS(presentationNS, "user-transformed"), false); - QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P3")); - QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout")); - QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm")); - QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "height", ""), QString("13.231cm")); - QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm")); - QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "y", ""), QString("5.838cm")); - - // of the subtitle frame - KoXmlElement subtitleBoxElement; - subtitleBoxElement = subtitleFrameElement.firstChild().toElement(); - QCOMPARE(subtitleBoxElement.isNull(), false); - QCOMPARE(subtitleBoxElement.isElement(), true); - QCOMPARE(subtitleBoxElement.parentNode().isNull(), false); - QCOMPARE(subtitleBoxElement.parentNode() == subtitleFrameElement, true); - QCOMPARE(KoXml::childNodesCount(subtitleBoxElement), 1); - QCOMPARE(subtitleBoxElement.firstChild().isNull(), false); - QCOMPARE(subtitleBoxElement.lastChild().isNull(), false); - QCOMPARE(subtitleBoxElement.previousSibling().isNull(), true); - QCOMPARE(subtitleBoxElement.nextSibling().isNull(), true); - QCOMPARE(subtitleBoxElement.localName(), QString("text-box")); - - // for the subtitle text-box - KoXmlElement subtitleParElement; - subtitleParElement = subtitleBoxElement.firstChild().toElement(); - QCOMPARE(subtitleParElement.isNull(), false); - QCOMPARE(subtitleParElement.isElement(), true); - QCOMPARE(subtitleParElement.parentNode().isNull(), false); - QCOMPARE(subtitleParElement.parentNode() == subtitleBoxElement, true); - QCOMPARE(KoXml::childNodesCount(subtitleParElement), 1); - QCOMPARE(subtitleParElement.firstChild().isNull(), false); - QCOMPARE(subtitleParElement.lastChild().isNull(), false); - QCOMPARE(subtitleParElement.previousSibling().isNull(), true); - QCOMPARE(subtitleParElement.nextSibling().isNull(), true); - QCOMPARE(subtitleParElement.localName(), QString("p")); - QCOMPARE(subtitleParElement.attributeNS(textNS, "style-name", ""), QString("P3")); - QCOMPARE(subtitleParElement.text(), QString("Foo")); -} - -void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentFormula() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // content.xml from a simple OpenDocument formula - // this is essentially MathML - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "E"; - xmlstream << "="; - xmlstream << ""; - xmlstream << "mc"; - xmlstream << "2"; - xmlstream << ""; - xmlstream << ""; - xmlstream << "E = mc^2 "; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* mathNS = "http://www.w3.org/1998/Math/MathML"; - - // - KoXmlElement mathElement; - mathElement = doc.documentElement(); - QCOMPARE(mathElement.isNull(), false); - QCOMPARE(mathElement.isElement(), true); - QCOMPARE(mathElement.parentNode().isNull(), false); - QCOMPARE(mathElement.parentNode().toDocument() == doc, true); - QCOMPARE(mathElement.firstChild().isNull(), false); - QCOMPARE(mathElement.lastChild().isNull(), false); - QCOMPARE(mathElement.previousSibling().isNull(), false); - QCOMPARE(mathElement.nextSibling().isNull(), true); - QCOMPARE(mathElement.localName(), QString("math")); - - // - KoXmlElement semanticsElement; - semanticsElement = KoXml::namedItemNS(mathElement, mathNS, "semantics"); - QCOMPARE(semanticsElement.isNull(), false); - QCOMPARE(semanticsElement.isElement(), true); - QCOMPARE(semanticsElement.parentNode().isNull(), false); - QCOMPARE(semanticsElement.parentNode().toElement() == mathElement, true); - QCOMPARE(semanticsElement.firstChild().isNull(), false); - QCOMPARE(semanticsElement.lastChild().isNull(), false); - QCOMPARE(semanticsElement.previousSibling().isNull(), true); - QCOMPARE(semanticsElement.nextSibling().isNull(), true); - QCOMPARE(semanticsElement.localName(), QString("semantics")); - - // the same but without namedItemNS - KoXmlElement semantics2Element; - semantics2Element = mathElement.firstChild().toElement(); - QCOMPARE(semantics2Element.isNull(), false); - QCOMPARE(semantics2Element.isElement(), true); - QCOMPARE(semantics2Element.parentNode().isNull(), false); - QCOMPARE(semantics2Element.parentNode().toElement() == mathElement, true); - QCOMPARE(semantics2Element.firstChild().isNull(), false); - QCOMPARE(semantics2Element.lastChild().isNull(), false); - QCOMPARE(semantics2Element.previousSibling().isNull(), true); - QCOMPARE(semantics2Element.nextSibling().isNull(), true); - QCOMPARE(semantics2Element.localName(), QString("semantics")); - - // - KoXmlElement mrowElement; - mrowElement = semanticsElement.firstChild().toElement(); - QCOMPARE(mrowElement.isNull(), false); - QCOMPARE(mrowElement.isElement(), true); - QCOMPARE(mrowElement.parentNode().isNull(), false); - QCOMPARE(mrowElement.parentNode().toElement() == semanticsElement, true); - QCOMPARE(mrowElement.firstChild().isNull(), false); - QCOMPARE(mrowElement.lastChild().isNull(), false); - QCOMPARE(mrowElement.previousSibling().isNull(), true); - QCOMPARE(mrowElement.nextSibling().isNull(), false); - QCOMPARE(mrowElement.localName(), QString("mrow")); - - // for "E" - KoXmlElement miElement; - miElement = mrowElement.firstChild().toElement(); - QCOMPARE(miElement.isNull(), false); - QCOMPARE(miElement.isElement(), true); - QCOMPARE(miElement.parentNode().isNull(), false); - QCOMPARE(miElement.parentNode().toElement() == mrowElement, true); - QCOMPARE(miElement.firstChild().isNull(), false); - QCOMPARE(miElement.lastChild().isNull(), false); - QCOMPARE(miElement.previousSibling().isNull(), true); - QCOMPARE(miElement.nextSibling().isNull(), false); - QCOMPARE(miElement.localName(), QString("mi")); - - // for "=" - KoXmlElement moElement; - moElement = miElement.nextSibling().toElement(); - QCOMPARE(moElement.isNull(), false); - QCOMPARE(moElement.isElement(), true); - QCOMPARE(moElement.parentNode().isNull(), false); - QCOMPARE(moElement.parentNode().toElement() == mrowElement, true); - QCOMPARE(moElement.firstChild().isNull(), false); - QCOMPARE(moElement.lastChild().isNull(), false); - QCOMPARE(moElement.previousSibling().isNull(), false); - QCOMPARE(moElement.nextSibling().isNull(), false); - QCOMPARE(moElement.localName(), QString("mo")); - QCOMPARE(moElement.attributeNS(mathNS, "stretchy", ""), QString("false")); - - // for "mc" and superscripted "2" - KoXmlElement msupElement; - msupElement = moElement.nextSibling().toElement(); - QCOMPARE(msupElement.isNull(), false); - QCOMPARE(msupElement.isElement(), true); - QCOMPARE(msupElement.parentNode().isNull(), false); - QCOMPARE(msupElement.parentNode().toElement() == mrowElement, true); - QCOMPARE(msupElement.firstChild().isNull(), false); - QCOMPARE(msupElement.lastChild().isNull(), false); - QCOMPARE(msupElement.previousSibling().isNull(), false); - QCOMPARE(msupElement.nextSibling().isNull(), true); - QCOMPARE(msupElement.localName(), QString("msup")); - - // inside the for "mc" - KoXmlElement mcElement; - mcElement = msupElement.firstChild().toElement(); - QCOMPARE(mcElement.isNull(), false); - QCOMPARE(mcElement.isElement(), true); - QCOMPARE(mcElement.parentNode().isNull(), false); - QCOMPARE(mcElement.parentNode().toElement() == msupElement, true); - QCOMPARE(mcElement.firstChild().isNull(), false); - QCOMPARE(mcElement.lastChild().isNull(), false); - QCOMPARE(mcElement.previousSibling().isNull(), true); - QCOMPARE(mcElement.nextSibling().isNull(), false); - QCOMPARE(mcElement.localName(), QString("mi")); - QCOMPARE(mcElement.text(), QString("mc")); - QCOMPARE(mcElement.attributeNS(mathNS, "fontstyle", ""), QString("italic")); - - // inside the for "2" (superscript) - KoXmlElement mnElement; - mnElement = mcElement.nextSibling().toElement(); - QCOMPARE(mnElement.isNull(), false); - QCOMPARE(mnElement.isElement(), true); - QCOMPARE(mnElement.parentNode().isNull(), false); - QCOMPARE(mnElement.parentNode().toElement() == msupElement, true); - QCOMPARE(mnElement.firstChild().isNull(), false); - QCOMPARE(mnElement.lastChild().isNull(), false); - QCOMPARE(mnElement.previousSibling().isNull(), false); - QCOMPARE(mnElement.nextSibling().isNull(), true); - QCOMPARE(mnElement.localName(), QString("mn")); - QCOMPARE(mnElement.text(), QString("2")); - - // - KoXmlElement annotationElement; - annotationElement = semanticsElement.lastChild().toElement(); - QCOMPARE(annotationElement.isNull(), false); - QCOMPARE(annotationElement.isElement(), true); - QCOMPARE(annotationElement.parentNode().isNull(), false); - QCOMPARE(annotationElement.parentNode().toElement() == semanticsElement, true); - QCOMPARE(annotationElement.firstChild().isNull(), false); - QCOMPARE(annotationElement.lastChild().isNull(), false); - QCOMPARE(annotationElement.previousSibling().isNull(), false); - QCOMPARE(annotationElement.nextSibling().isNull(), true); - QCOMPARE(annotationElement.localName(), QString("annotation")); - QCOMPARE(annotationElement.text(), QString("E = mc^2 ")); - QCOMPARE(annotationElement.attributeNS(mathNS, "encoding", ""), QString("StarMath 5.0")); -} - -void TestXmlReaderWithoutSpaces::testLargeOpenDocumentSpreadsheet() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - int sheetCount = 4; - int rowCount = 200; - int colCount = 200 / 16; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // content.xml - xmlstream << "\n"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - for (int i = 0; i < sheetCount; i++) { - QString sheetName = QString("Sheet%1").arg(i + 1); - xmlstream << ""; - for (int j = 0; j < rowCount; j++) { - xmlstream << ""; - for (int k = 0; k < colCount; k++) { - xmlstream << ""; - xmlstream << "Hello, world"; - xmlstream << ""; - } - xmlstream << ""; - } - xmlstream << ""; - } - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - printf("Raw XML size: %lld KB\n", xmldevice.size() / 1024); - - - QTime timer; - -#if 0 - // just to test parsing speed with plain dumb handler - QXmlStreamReader *reader = new QXmlStreamReader(xmldevice); - reader->setNamespaceProcessing(true); - timer.start(); - ParseError error = parseDocument(*reader, doc); - printf("Large spreadsheet: QXmlStreamReader parsing time is %d ms\n", timer.elapsed()); - delete reader; - xmldevice.seek(0); -#endif - - KoXmlDocument doc(false); - - timer.start(); - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - if (!errorMsg.isEmpty()) { - qDebug("Error: %s", qPrintable(errorMsg)); - return; - } - - printf("Large spreadsheet: KoXmlDocument parsing time is %d ms\n", timer.elapsed()); - - // release memory taken by the XML document content - //xmlstream.setDevice( 0 ); - - // namespaces that will be used - QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; - QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - - // - KoXmlElement bodyElement; - bodyElement = contentElement.firstChild().toElement(); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - // - KoXmlElement spreadsheetElement; - spreadsheetElement = bodyElement.firstChild().toElement(); - QCOMPARE(spreadsheetElement.isNull(), false); - QCOMPARE(spreadsheetElement.isElement(), true); - QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); - - // now we visit every sheet, every row, every cell - timer.start(); - KoXmlElement tableElement; - tableElement = spreadsheetElement.firstChild().toElement(); - for (int table = 0; table < sheetCount; table++) { - QString tableName = QString("Sheet%1").arg(table + 1); - QCOMPARE(tableElement.isNull(), false); - QCOMPARE(tableElement.isElement(), true); - QCOMPARE(tableElement.localName(), QString("table")); - QCOMPARE(tableElement.hasAttributeNS(tableNS, "name"), true); - QCOMPARE(tableElement.attributeNS(tableNS, "name", ""), tableName); - QCOMPARE(tableElement.attributeNS(tableNS, "print", ""), QString("false")); - - // load everything for this table - //KoXml::load( tableElement, 99 ); - - QCOMPARE(tableElement.parentNode().isNull(), false); - QCOMPARE(tableElement.parentNode() == spreadsheetElement, true); - QCOMPARE(tableElement.firstChild().isNull(), false); - QCOMPARE(tableElement.lastChild().isNull(), false); - - KoXmlElement rowElement; - rowElement = tableElement.firstChild().toElement(); - for (int row = 0; row < rowCount; row++) { - QCOMPARE(rowElement.isNull(), false); - QCOMPARE(rowElement.isElement(), true); - QCOMPARE(rowElement.localName(), QString("table-row")); - QCOMPARE(rowElement.parentNode().isNull(), false); - QCOMPARE(rowElement.parentNode() == tableElement, true); - QCOMPARE(rowElement.firstChild().isNull(), false); - QCOMPARE(rowElement.lastChild().isNull(), false); - - KoXmlElement cellElement; - cellElement = rowElement.firstChild().toElement(); - for (int col = 0; col < colCount; col++) { - QCOMPARE(cellElement.isNull(), false); - QCOMPARE(cellElement.isElement(), true); - QCOMPARE(cellElement.localName(), QString("table-cell")); - QCOMPARE(cellElement.text(), QString("Hello, world")); - QCOMPARE(cellElement.hasAttributeNS(officeNS, "value-type"), true); - QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string")); - QCOMPARE(cellElement.parentNode().isNull(), false); - QCOMPARE(cellElement.parentNode() == rowElement, true); - QCOMPARE(cellElement.firstChild().isNull(), false); - QCOMPARE(cellElement.lastChild().isNull(), false); - cellElement = cellElement.nextSibling().toElement(); - } - - //KoXml::unload( rowElement ); - rowElement = rowElement.nextSibling().toElement(); - } - - KoXml::unload(tableElement); - tableElement = tableElement.nextSibling().toElement(); - } - - printf("Large spreadsheet: iterating time is %d ms\n", timer.elapsed()); -} - -void TestXmlReaderWithoutSpaces::testExternalOpenDocumentSpreadsheet(const QString& filename) -{ - QProcess unzip; - QStringList arguments; - arguments << "-o" << filename << "content.xml"; - - printf("Unzipping content.xml from %s...\n", qPrintable(filename)); - - unzip.start("unzip", arguments); - if (!unzip.waitForStarted()) { - printf("Error: can't invoke unzip. Check your PATH and installation!\n\n"); - return; - } - - if (!unzip.waitForFinished()) { - printf("Error: unzip failed, can't continue!\n\n"); - return; - } - - printf("Procesing content.xml....\n"); - - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QFile xmlfile("content.xml"); - if (!xmlfile.open(QFile::ReadOnly)) { - printf("Can not open file '%s'\n", qPrintable(filename)); - return; - } - - printf("Test external file: %s %lld KB\n", qPrintable(filename), xmlfile.size() / 1024); - - QTime timer; - timer.start(); - - KoXmlDocument doc(false); - - QCOMPARE(KoXml::setDocument(doc, &xmlfile, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - printf("External spreadsheet: parsing time is %d ms\n", timer.elapsed()); - - // namespaces that will be used - QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; - QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - - long totalCellCount = 0; - - KoXmlElement bodyElement; - forEachElement(bodyElement, contentElement) { - // - if (bodyElement.localName() != QString("body")) - continue; - - // now we iterate inside the body - timer.start(); - - // - KoXmlElement spreadsheetElement; - spreadsheetElement = bodyElement.firstChild().toElement(); - QCOMPARE(spreadsheetElement.isNull(), false); - QCOMPARE(spreadsheetElement.isElement(), true); - QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); - - // now we visit every sheet - long tableCount = -1; - KoXmlElement tableElement; - tableElement = spreadsheetElement.firstChild().toElement(); - for (;;) { - if (tableElement.isNull()) - break; - - if (tableElement.localName() != QString("table")) { - tableElement = tableElement.nextSibling().toElement(); - continue; - } - - QString tableName = tableElement.attributeNS(tableNS, "name", ""); - tableCount++; - - printf(" sheet #%ld (%s): ", tableCount + 1, qPrintable(tableName)); - - // use to preload everything in this sheet, will slow it down! - // KoXml::load( tableElement, 50 ); - - long rowCount = -1; - long cellCount = -1; - - KoXmlElement rowElement; - rowElement = tableElement.firstChild().toElement(); - for (;;) { - if (rowElement.isNull()) - break; - - if (rowElement.localName() != QString("table-row")) { - rowElement = rowElement.nextSibling().toElement(); - continue; - } - - rowCount++; - KoXml::load(rowElement, 4); - - QCOMPARE(rowElement.isElement(), true); - QCOMPARE(rowElement.localName(), QString("table-row")); - QCOMPARE(rowElement.parentNode().isNull(), false); - QCOMPARE(rowElement.parentNode() == tableElement, true); - - KoXmlElement cellElement; - cellElement = rowElement.firstChild().toElement(); - for (; ;) { - if (cellElement.isNull()) - break; - - if (cellElement.localName() != QString("table-cell")) { - cellElement = cellElement.nextSibling().toElement(); - continue; - } - - cellCount++; - - QCOMPARE(cellElement.isNull(), false); - QCOMPARE(cellElement.isElement(), true); - QCOMPARE(cellElement.localName(), QString("table-cell")); - QString text1 = cellElement.text(); - QString text2 = cellElement.text(); - QCOMPARE(text1, text2); - QString type1 = cellElement.attributeNS(officeNS, "value-type", QString()); - QString type2 = cellElement.attributeNS(officeNS, "value-type", QString()); - QCOMPARE(type1, type2); - QString style1 = cellElement.attributeNS(tableNS, "style-name", QString()); - QString style2 = cellElement.attributeNS(tableNS, "style-name", QString()); - QCOMPARE(style1, style2); - - QCOMPARE(cellElement.parentNode().isNull(), false); - QCOMPARE(cellElement.parentNode() == rowElement, true); - - cellElement = cellElement.nextSibling().toElement(); - } - - - // better not to unload, freeing memory takes time - KoXml::unload(rowElement); - - rowElement = rowElement.nextSibling().toElement(); - } - - printf(" %ld rows, %ld cells\n", rowCount + 1, cellCount + 1); - totalCellCount += (cellCount + 1); - - // IMPORTANT: helps minimizing memory usage !! - // we do not need that element anymore, so just throw it away - KoXml::unload(tableElement); - - tableElement = tableElement.nextSibling().toElement(); - } - - KoXml::unload(spreadsheetElement); - } - - printf("Total number of cells: %ld\n", totalCellCount); - - int elapsed = timer.elapsed(); - printf("External spreadsheet: iterating time is %d ms\n", elapsed); - if (elapsed > 0) - printf(" approx. %ld cells/second\n", totalCellCount*1000 / elapsed); - - // uncomment to check the XML - xmlfile.remove(); -} - -QTEST_GUILESS_MAIN(TestXmlReaderWithoutSpaces) -#include - diff --git a/libs/pigment/CMakeLists.txt b/libs/pigment/CMakeLists.txt index c44fcb81c4..b1552b6da6 100644 --- a/libs/pigment/CMakeLists.txt +++ b/libs/pigment/CMakeLists.txt @@ -1,122 +1,124 @@ 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/pigment/KoColorConversionAlphaTransformation.cpp b/libs/pigment/KoColorConversionAlphaTransformation.cpp index 5289cafe5d..0e9e23e919 100644 --- a/libs/pigment/KoColorConversionAlphaTransformation.cpp +++ b/libs/pigment/KoColorConversionAlphaTransformation.cpp @@ -1,347 +1,351 @@ /* * Copyright (c) 2007 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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorConversionAlphaTransformation.h" #include "KoColorSpace.h" #include "KoIntegerMaths.h" #include "KoColorSpaceTraits.h" #include "KoColorModelStandardIds.h" #include "KoColorModelStandardIdsUtils.h" /** * Converter from the alpha color space to any color space */ template class KoColorConversionFromAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionFromAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const alpha_channel_type *srcPtr = reinterpret_cast(src); quint16 data[4]; const qint32 pixelSize = dstColorSpace()->pixelSize(); data[1] = UINT16_MAX / 2; // a data[2] = UINT16_MAX / 2; // b data[3] = UINT16_MAX; // A while (nPixels > 0) { data[0] = KoColorSpaceMaths::scaleToA(*srcPtr); // L dstColorSpace()->fromLabA16((quint8*)data, dst, 1); srcPtr++; dst += pixelSize; nPixels--; } } }; template class KoColorConversionAlphaToLab16Transformation : public KoColorConversionTransformation { public: KoColorConversionAlphaToLab16Transformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const alpha_channel_type *srcPtr = reinterpret_cast(src); quint16 *dstPtr = reinterpret_cast(dst); while (nPixels > 0) { dstPtr[0] = KoColorSpaceMaths::scaleToA(*srcPtr); // L dstPtr[1] = UINT16_MAX / 2; // a dstPtr[2] = UINT16_MAX / 2; // b dstPtr[3] = UINT16_MAX; // A srcPtr++; dstPtr += 4; nPixels--; } } }; template class KoColorConversionGrayAFromAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionGrayAFromAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const alpha_channel_type *srcPtr = reinterpret_cast(src); gray_channel_type *dstPtr = reinterpret_cast(dst); while (nPixels > 0) { dstPtr[0] = KoColorSpaceMaths::scaleToA(*srcPtr); dstPtr[1] = KoColorSpaceMathsTraits::unitValue; srcPtr++; dstPtr += 2; nPixels--; } } }; //------ KoColorConversionFromAlphaTransformationFactoryImpl ------// template KoColorConversionFromAlphaTransformationFactoryImpl:: KoColorConversionFromAlphaTransformationFactoryImpl(const QString& _dstModelId, const QString& _dstDepthId, const QString& _dstProfileName) : KoColorConversionTransformationFactory(AlphaColorModelID.id(), colorDepthIdForChannelType().id(), "default", _dstModelId, _dstDepthId, _dstProfileName) { } template KoColorConversionTransformation* KoColorConversionFromAlphaTransformationFactoryImpl:: createColorTransformation(const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT(canBeSource(srcColorSpace)); Q_ASSERT(canBeDestination(dstColorSpace)); if (dstColorSpace->colorModelId() == GrayAColorModelID && dstColorSpace->colorDepthId() == Integer8BitsColorDepthID) { return new KoColorConversionGrayAFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else if (dstColorSpace->colorModelId() == GrayAColorModelID && dstColorSpace->colorDepthId() == Integer16BitsColorDepthID) { return new KoColorConversionGrayAFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); + #ifdef HAVE_OPENEXR } else if (dstColorSpace->colorModelId() == GrayAColorModelID && dstColorSpace->colorDepthId() == Float16BitsColorDepthID) { return new KoColorConversionGrayAFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); + #endif } else if (dstColorSpace->colorModelId() == GrayAColorModelID && dstColorSpace->colorDepthId() == Float32BitsColorDepthID) { return new KoColorConversionGrayAFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else if (dstColorSpace->colorModelId() == LABAColorModelID && dstColorSpace->colorDepthId() == Integer16BitsColorDepthID) { return new KoColorConversionAlphaToLab16Transformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else { return new KoColorConversionFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } } template bool KoColorConversionFromAlphaTransformationFactoryImpl:: conserveColorInformation() const { return false; } template bool KoColorConversionFromAlphaTransformationFactoryImpl:: conserveDynamicRange() const { return false; } template class KoColorConversionFromAlphaTransformationFactoryImpl; template class KoColorConversionFromAlphaTransformationFactoryImpl; #ifdef HAVE_OPENEXR template class KoColorConversionFromAlphaTransformationFactoryImpl; #endif template class KoColorConversionFromAlphaTransformationFactoryImpl; //------ KoColorConversionToAlphaTransformation ------// /** * Converter to the alpha color space to any color space */ template class KoColorConversionToAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionToAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { alpha_channel_type *dstPtr = reinterpret_cast(dst); quint16 data[4]; qint32 pixelSize = srcColorSpace()->pixelSize(); while (nPixels > 0) { srcColorSpace()->toLabA16(src, (quint8*)data, 1); *dstPtr = KoColorSpaceMaths::scaleToA(UINT16_MULT(data[0], data[3])); // L * A src += pixelSize; dstPtr ++; nPixels --; } } }; template class KoColorConversionLab16ToAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionLab16ToAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const quint16 *srcPtr = reinterpret_cast(src); alpha_channel_type *dstPtr = reinterpret_cast(dst); while (nPixels > 0) { *dstPtr = KoColorSpaceMaths::scaleToA(UINT16_MULT(srcPtr[0], srcPtr[3])); // L * A srcPtr += 4; dstPtr++; nPixels--; } } }; //------ KoColorConversionGrayAU8ToAlphaTransformation ------// template class KoColorConversionGrayAToAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionGrayAToAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const gray_channel_type *srcPtr = reinterpret_cast(src); alpha_channel_type *dstPtr = reinterpret_cast(dst); while (nPixels > 0) { *dstPtr = KoColorSpaceMaths::scaleToA( KoColorSpaceMaths::multiply(srcPtr[0], srcPtr[1])); srcPtr += 2; dstPtr++; nPixels --; } } }; //------ KoColorConversionToAlphaTransformationFactoryImpl ------// template KoColorConversionToAlphaTransformationFactoryImpl:: KoColorConversionToAlphaTransformationFactoryImpl(const QString& _srcModelId, const QString& _srcDepthId, const QString& _srcProfileName) : KoColorConversionTransformationFactory(_srcModelId, _srcDepthId, _srcProfileName, AlphaColorModelID.id(), colorDepthIdForChannelType().id(), "default") { } template KoColorConversionTransformation* KoColorConversionToAlphaTransformationFactoryImpl:: createColorTransformation(const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT(canBeSource(srcColorSpace)); Q_ASSERT(canBeDestination(dstColorSpace)); if (srcColorSpace->colorModelId() == GrayAColorModelID && srcColorSpace->colorDepthId() == Integer8BitsColorDepthID) { return new KoColorConversionGrayAToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else if (srcColorSpace->colorModelId() == GrayAColorModelID && srcColorSpace->colorDepthId() == Integer16BitsColorDepthID) { return new KoColorConversionGrayAToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); +#ifdef HAVE_OPENEXR } else if (srcColorSpace->colorModelId() == GrayAColorModelID && srcColorSpace->colorDepthId() == Float16BitsColorDepthID) { return new KoColorConversionGrayAToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); +#endif } else if (srcColorSpace->colorModelId() == GrayAColorModelID && srcColorSpace->colorDepthId() == Float32BitsColorDepthID) { return new KoColorConversionGrayAToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else if (srcColorSpace->colorModelId() == LABAColorModelID && srcColorSpace->colorDepthId() == Integer16BitsColorDepthID) { return new KoColorConversionLab16ToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else { return new KoColorConversionToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } } template bool KoColorConversionToAlphaTransformationFactoryImpl:: conserveColorInformation() const { return false; } template bool KoColorConversionToAlphaTransformationFactoryImpl:: conserveDynamicRange() const { return false; } template class KoColorConversionToAlphaTransformationFactoryImpl; template class KoColorConversionToAlphaTransformationFactoryImpl; #ifdef HAVE_OPENEXR template class KoColorConversionToAlphaTransformationFactoryImpl; #endif template class KoColorConversionToAlphaTransformationFactoryImpl; diff --git a/libs/pigment/KoColorConversionSystem.cpp b/libs/pigment/KoColorConversionSystem.cpp index c55db3a0e7..0344044b11 100644 --- a/libs/pigment/KoColorConversionSystem.cpp +++ b/libs/pigment/KoColorConversionSystem.cpp @@ -1,504 +1,505 @@ /* * Copyright (c) 2007-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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorConversionSystem.h" #include "KoColorConversionSystem_p.h" #include #include #include "KoColorConversionAlphaTransformation.h" #include "KoColorConversionTransformation.h" #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoCopyColorConversionTransformation.h" #include "KoMultipleColorConversionTransformation.h" -KoColorConversionSystem::KoColorConversionSystem() : d(new Private) +KoColorConversionSystem::KoColorConversionSystem(RegistryInterface *registryInterface) + : d(new Private(registryInterface)) { } KoColorConversionSystem::~KoColorConversionSystem() { qDeleteAll(d->graph); qDeleteAll(d->vertexes); delete d; } void KoColorConversionSystem::connectToEngine(Node* _node, Node* _engine) { Vertex* v1 = createVertex(_node, _engine); Vertex* v2 = createVertex(_engine, _node); v1->conserveColorInformation = !_node->isGray; v2->conserveColorInformation = !_node->isGray; v1->conserveDynamicRange = _engine->isHdr; v2->conserveDynamicRange = _engine->isHdr; } KoColorConversionSystem::Node* KoColorConversionSystem::insertEngine(const KoColorSpaceEngine* engine) { NodeKey key(engine->id(), engine->id(), engine->id()); Node* n = new Node; n->modelId = engine->id(); n->depthId = engine->id(); n->profileName = engine->id(); n->referenceDepth = 64; // engine don't have reference depth, d->graph.insert(key, n); n->init(engine); return n; } void KoColorConversionSystem::insertColorSpace(const KoColorSpaceFactory* csf) { dbgPigment << "Inserting color space " << csf->name() << " (" << csf->id() << ") Model: " << csf->colorModelId() << " Depth: " << csf->colorDepthId() << " into the CCS"; - const QList profiles = KoColorSpaceRegistry::instance()->profilesFor(csf); + const QList profiles = d->registryInterface->profilesFor(csf); QString modelId = csf->colorModelId().id(); QString depthId = csf->colorDepthId().id(); if (profiles.isEmpty()) { // There is no profile for this CS, create a node without profile name if the color engine isn't icc-based if (csf->colorSpaceEngine() != "icc") { Node* n = nodeFor(modelId, depthId, "default"); n->init(csf); } else { dbgPigment << "Cannot add node for " << csf->name() << ", since there are no profiles available"; } } else { // Initialise the nodes Q_FOREACH (const KoColorProfile* profile, profiles) { Node* n = nodeFor(modelId, depthId, profile->name()); n->init(csf); if (!csf->colorSpaceEngine().isEmpty()) { KoColorSpaceEngine* engine = KoColorSpaceEngineRegistry::instance()->get(csf->colorSpaceEngine()); Q_ASSERT(engine); NodeKey engineKey(engine->id(), engine->id(), engine->id()); Node* engineNode = 0; QHash::ConstIterator it = d->graph.constFind(engineKey); if (it != d->graph.constEnd()) { engineNode = it.value(); } else { engineNode = insertEngine(engine); } connectToEngine(n, engineNode); } } } // Construct a link for "custom" transformation const QList cctfs = csf->colorConversionLinks(); Q_FOREACH (KoColorConversionTransformationFactory* cctf, cctfs) { Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile()); Q_ASSERT(srcNode); Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile()); Q_ASSERT(dstNode); // Check if the two nodes are already connected Vertex* v = vertexBetween(srcNode, dstNode); // If the vertex doesn't already exist, then create it if (!v) { v = createVertex(srcNode, dstNode); } Q_ASSERT(v); // we should have one now if (dstNode->modelId == modelId && dstNode->depthId == depthId) { v->setFactoryFromDst(cctf); } if (srcNode->modelId == modelId && srcNode->depthId == depthId) { v->setFactoryFromSrc(cctf); } } } void KoColorConversionSystem::insertColorProfile(const KoColorProfile* _profile) { dbgPigmentCCS << _profile->name(); - const QList< const KoColorSpaceFactory* >& factories = KoColorSpaceRegistry::instance()->colorSpacesFor(_profile); + const QList< const KoColorSpaceFactory* >& factories = d->registryInterface->colorSpacesFor(_profile); Q_FOREACH (const KoColorSpaceFactory* factory, factories) { QString modelId = factory->colorModelId().id(); QString depthId = factory->colorDepthId().id(); Node* n = nodeFor(modelId, depthId, _profile->name()); n->init(factory); if (!factory->colorSpaceEngine().isEmpty()) { KoColorSpaceEngine* engine = KoColorSpaceEngineRegistry::instance()->get(factory->colorSpaceEngine()); Q_ASSERT(engine); Node* engineNode = d->graph[ NodeKey(engine->id(), engine->id(), engine->id())]; Q_ASSERT(engineNode); connectToEngine(n, engineNode); } const QList cctfs = factory->colorConversionLinks(); Q_FOREACH (KoColorConversionTransformationFactory* cctf, cctfs) { Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile()); Q_ASSERT(srcNode); Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile()); Q_ASSERT(dstNode); if (srcNode == n || dstNode == n) { // Check if the two nodes are already connected Vertex* v = vertexBetween(srcNode, dstNode); // If the vertex doesn't already exist, then create it if (!v) { v = createVertex(srcNode, dstNode); } Q_ASSERT(v); // we should have one now if (dstNode->modelId == modelId && dstNode->depthId == depthId) { v->setFactoryFromDst(cctf); } if (srcNode->modelId == modelId && srcNode->depthId == depthId) { v->setFactoryFromSrc(cctf); } } } } } const KoColorSpace* KoColorConversionSystem::defaultColorSpaceForNode(const Node* node) const { - return KoColorSpaceRegistry::instance()->colorSpace(node->modelId, node->depthId, node->profileName); + return d->registryInterface->colorSpace(node->modelId, node->depthId, node->profileName); } KoColorConversionSystem::Node* KoColorConversionSystem::createNode(const QString& _modelId, const QString& _depthId, const QString& _profileName) { Node* n = new Node; n->modelId = _modelId; n->depthId = _depthId; n->profileName = _profileName; d->graph.insert(NodeKey(_modelId, _depthId, _profileName), n); return n; } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const KoColorSpace* _colorSpace) const { const KoColorProfile* profile = _colorSpace->profile(); return nodeFor(_colorSpace->colorModelId().id(), _colorSpace->colorDepthId().id(), profile ? profile->name() : "default"); } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) const { dbgPigmentCCS << "Look for node: " << _colorModelId << " " << _colorDepthId << " " << _profileName; return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName)); } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const NodeKey& key) const { dbgPigmentCCS << "Look for node: " << key.modelId << " " << key.depthId << " " << key.profileName << " " << d->graph.value(key); return d->graph.value(key); } KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) { return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName)); } KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const KoColorConversionSystem::NodeKey& key) { QHash::ConstIterator it = d->graph.constFind(key); if (it != d->graph.constEnd()) { return it.value(); } else { return createNode(key.modelId, key.depthId, key.profileName); } } QList KoColorConversionSystem::nodesFor(const QString& _modelId, const QString& _depthId) { QList nodes; Q_FOREACH (Node* node, d->graph) { if (node->modelId == _modelId && node->depthId == _depthId) { nodes << node; } } return nodes; } KoColorConversionTransformation* KoColorConversionSystem::createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*srcColorSpace == *dstColorSpace) { return new KoCopyColorConversionTransformation(srcColorSpace); } Q_ASSERT(srcColorSpace); Q_ASSERT(dstColorSpace); dbgPigmentCCS << srcColorSpace->id() << (srcColorSpace->profile() ? srcColorSpace->profile()->name() : "default"); dbgPigmentCCS << dstColorSpace->id() << (dstColorSpace->profile() ? dstColorSpace->profile()->name() : "default"); Path path = findBestPath( nodeFor(srcColorSpace), nodeFor(dstColorSpace)); Q_ASSERT(path.length() > 0); KoColorConversionTransformation* transfo = createTransformationFromPath(path, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); Q_ASSERT(*transfo->srcColorSpace() == *srcColorSpace); Q_ASSERT(*transfo->dstColorSpace() == *dstColorSpace); Q_ASSERT(transfo); return transfo; } void KoColorConversionSystem::createColorConverters(const KoColorSpace* colorSpace, const QList< QPair >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const { // TODO This function currently only select the best conversion only based on the transformation // from colorSpace to one of the color spaces in the list, but not the other way around // it might be worth to look also the return path. const Node* csNode = nodeFor(colorSpace); PathQualityChecker pQC(csNode->referenceDepth, !csNode->isHdr, !csNode->isGray); // Look for a color conversion Path bestPath; typedef QPair KoID2KoID; Q_FOREACH (const KoID2KoID & possibility, possibilities) { - const KoColorSpaceFactory* csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(KoColorSpaceRegistry::instance()->colorSpaceId(possibility.first.id(), possibility.second.id())); + const KoColorSpaceFactory* csf = d->registryInterface->colorSpaceFactory(possibility.first.id(), possibility.second.id()); if (csf) { Path path = findBestPath(csNode, nodeFor(csf->colorModelId().id(), csf->colorDepthId().id(), csf->defaultProfile())); Q_ASSERT(path.length() > 0); path.isGood = pQC.isGoodPath(path); if (bestPath.isEmpty()) { bestPath = path; } else if ((!bestPath.isGood && path.isGood) || pQC.lessWorseThan(path, bestPath)) { bestPath = path; } } } Q_ASSERT(!bestPath.isEmpty()); const KoColorSpace* endColorSpace = defaultColorSpaceForNode(bestPath.endNode()); fromCS = createTransformationFromPath(bestPath, colorSpace, endColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); Path returnPath = findBestPath(bestPath.endNode(), csNode); Q_ASSERT(!returnPath.isEmpty()); toCS = createTransformationFromPath(returnPath, endColorSpace, colorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); Q_ASSERT(*toCS->dstColorSpace() == *fromCS->srcColorSpace()); Q_ASSERT(*fromCS->dstColorSpace() == *toCS->srcColorSpace()); } KoColorConversionTransformation* KoColorConversionSystem::createTransformationFromPath(const Path &path, const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT(srcColorSpace->colorModelId().id() == path.startNode()->modelId); Q_ASSERT(srcColorSpace->colorDepthId().id() == path.startNode()->depthId); Q_ASSERT(dstColorSpace->colorModelId().id() == path.endNode()->modelId); Q_ASSERT(dstColorSpace->colorDepthId().id() == path.endNode()->depthId); KoColorConversionTransformation* transfo; const QList< Path::node2factory > pathOfNode = path.compressedPath(); if (pathOfNode.size() == 2) { // Direct connection transfo = pathOfNode[1].second->createColorTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else { KoMultipleColorConversionTransformation* mccTransfo = new KoMultipleColorConversionTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); transfo = mccTransfo; // Get the first intermediary color space dbgPigmentCCS << pathOfNode[ 0 ].first->id() << " to " << pathOfNode[ 1 ].first->id(); const KoColorSpace* intermCS = defaultColorSpaceForNode(pathOfNode[1].first); mccTransfo->appendTransfo(pathOfNode[1].second->createColorTransformation(srcColorSpace, intermCS, renderingIntent, conversionFlags)); for (int i = 2; i < pathOfNode.size() - 1; i++) { dbgPigmentCCS << pathOfNode[ i - 1 ].first->id() << " to " << pathOfNode[ i ].first->id(); const KoColorSpace* intermCS2 = defaultColorSpaceForNode(pathOfNode[i].first); Q_ASSERT(intermCS2); mccTransfo->appendTransfo(pathOfNode[i].second->createColorTransformation(intermCS, intermCS2, renderingIntent, conversionFlags)); intermCS = intermCS2; } dbgPigmentCCS << pathOfNode[ pathOfNode.size() - 2 ].first->id() << " to " << pathOfNode[ pathOfNode.size() - 1 ].first->id(); mccTransfo->appendTransfo(pathOfNode.last().second->createColorTransformation(intermCS, dstColorSpace, renderingIntent, conversionFlags)); } return transfo; } KoColorConversionSystem::Vertex* KoColorConversionSystem::vertexBetween(KoColorConversionSystem::Node* srcNode, KoColorConversionSystem::Node* dstNode) { Q_FOREACH (Vertex* oV, srcNode->outputVertexes) { if (oV->dstNode == dstNode) { return oV; } } return 0; } KoColorConversionSystem::Vertex* KoColorConversionSystem::createVertex(Node* srcNode, Node* dstNode) { Vertex* v = new Vertex(srcNode, dstNode); srcNode->outputVertexes.append(v); d->vertexes.append(v); return v; } // -- Graph visualization functions -- QString KoColorConversionSystem::vertexToDot(KoColorConversionSystem::Vertex* v, const QString &options) const { return QString(" \"%1\" -> \"%2\" %3\n").arg(v->srcNode->id()).arg(v->dstNode->id()).arg(options); } QString KoColorConversionSystem::toDot() const { QString dot = "digraph CCS {\n"; Q_FOREACH (Vertex* oV, d->vertexes) { dot += vertexToDot(oV, "default") ; } dot += "}\n"; return dot; } bool KoColorConversionSystem::existsPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const { dbgPigmentCCS << "srcModelId = " << srcModelId << " srcDepthId = " << srcDepthId << " srcProfileName = " << srcProfileName << " dstModelId = " << dstModelId << " dstDepthId = " << dstDepthId << " dstProfileName = " << dstProfileName; const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName); const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName); if (srcNode == dstNode) return true; if (!srcNode) return false; if (!dstNode) return false; Path path = findBestPath(srcNode, dstNode); bool exist = !path.isEmpty(); return exist; } bool KoColorConversionSystem::existsGoodPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const { const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName); const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName); if (srcNode == dstNode) return true; if (!srcNode) return false; if (!dstNode) return false; Path path = findBestPath(srcNode, dstNode); bool existAndGood = path.isGood; return existAndGood; } QString KoColorConversionSystem::bestPathToDot(const QString& srcKey, const QString& dstKey) const { const Node* srcNode = 0; const Node* dstNode = 0; Q_FOREACH (Node* node, d->graph) { if (node->id() == srcKey) { srcNode = node; } if (node->id() == dstKey) { dstNode = node; } } Path p = findBestPath(srcNode, dstNode); Q_ASSERT(!p.isEmpty()); QString dot = "digraph CCS {\n" + QString(" \"%1\" [color=red]\n").arg(srcNode->id()) + QString(" \"%1\" [color=red]\n").arg(dstNode->id()); Q_FOREACH (Vertex* oV, d->vertexes) { QString options; if (p.vertexes.contains(oV)) { options = "[color=red]"; } dot += vertexToDot(oV, options) ; } dot += "}\n"; return dot; } inline KoColorConversionSystem::Path KoColorConversionSystem::findBestPathImpl2(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode, bool ignoreHdr, bool ignoreColorCorrectness) const { PathQualityChecker pQC(qMin(srcNode->referenceDepth, dstNode->referenceDepth), ignoreHdr, ignoreColorCorrectness); Node2PathHash node2path; // current best path to reach a given node QList possiblePaths; // list of all paths // Generate the initial list of paths Q_FOREACH (Vertex* v, srcNode->outputVertexes) { if (v->dstNode->isInitialized) { Path p; p.appendVertex(v); Node* endNode = v->dstNode; if (endNode == dstNode) { Q_ASSERT(pQC.isGoodPath(p)); // <- it's a direct link, it has to be a good path p.isGood = true; return p; } else { Q_ASSERT(!node2path.contains(endNode)); // That would be a total fuck up if there are two vertexes between two nodes node2path.insert(endNode, p); possiblePaths.append(p); } } } Path currentBestPath; // Continue while there are any possibilities remaining while (possiblePaths.size() > 0) { // Loop through all paths and explore one step further const QList currentPaths = possiblePaths; for (const Path &p : currentPaths) { const Node* endNode = p.endNode(); for (Vertex* v : endNode->outputVertexes) { if (v->dstNode->isInitialized && !p.contains(v->dstNode)) { Path newP = p; // Candidate newP.appendVertex(v); Node* newEndNode = v->dstNode; if (newEndNode == dstNode) { if (pQC.isGoodPath(newP)) { // Victory newP.isGood = true; return newP; } else if (pQC.lessWorseThan(newP, currentBestPath)) { Q_ASSERT(newP.startNode()->id() == currentBestPath.startNode()->id()); Q_ASSERT(newP.endNode()->id() == currentBestPath.endNode()->id()); // Can we do better than dumping memory values??? // warnPigment << pQC.lessWorseThan(newP, currentBestPath) << " " << newP << " " << currentBestPath; currentBestPath = newP; } } else { // This is an incomplete path. Check if there's a better way to get to its endpoint. Node2PathHash::Iterator it = node2path.find(newEndNode); if (it != node2path.end()) { Path &p2 = it.value(); if (pQC.lessWorseThan(newP, p2)) { p2 = newP; possiblePaths.append(newP); } } else { node2path.insert(newEndNode, newP); possiblePaths.append(newP); } } } } possiblePaths.removeAll(p); // Remove from list of remaining paths } } if (!currentBestPath.isEmpty()) { warnPigment << "No good path from " << srcNode->id() << " to " << dstNode->id() << " found : length = " << currentBestPath.length() << " cost = " << currentBestPath.cost << " referenceDepth = " << currentBestPath.referenceDepth << " respectColorCorrectness = " << currentBestPath.respectColorCorrectness << " isGood = " << currentBestPath.isGood ; return currentBestPath; } errorPigment << "No path from " << srcNode->id() << " to " << dstNode->id() << " found not "; return currentBestPath; } inline KoColorConversionSystem::Path KoColorConversionSystem::findBestPathImpl(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode, bool ignoreHdr) const { Q_ASSERT(srcNode); Q_ASSERT(dstNode); return findBestPathImpl2(srcNode, dstNode, ignoreHdr, (srcNode->isGray || dstNode->isGray)); } KoColorConversionSystem::Path KoColorConversionSystem::findBestPath(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode) const { Q_ASSERT(srcNode); Q_ASSERT(dstNode); dbgPigmentCCS << "Find best path between " << srcNode->id() << " and " << dstNode->id(); if (srcNode->isHdr && dstNode->isHdr) { return findBestPathImpl(srcNode, dstNode, false); } else { return findBestPathImpl(srcNode, dstNode, true); } } diff --git a/libs/pigment/KoColorConversionSystem.h b/libs/pigment/KoColorConversionSystem.h index 086d6795b9..49b39b9889 100644 --- a/libs/pigment/KoColorConversionSystem.h +++ b/libs/pigment/KoColorConversionSystem.h @@ -1,180 +1,190 @@ /* * Copyright (c) 2007-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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_COLOR_CONVERSION_SYSTEM_H_ #define _KO_COLOR_CONVERSION_SYSTEM_H_ class KoColorProfile; class KoColorSpace; class KoColorSpaceFactory; class KoColorSpaceEngine; class KoID; #include "KoColorConversionTransformation.h" #include #include #include "kritapigment_export.h" /** * This class hold the logic related to pigment's Color Conversion System. It's * basically a graph containing all the possible color transformation between * the color spaces. The most useful functions are createColorConverter to create * a color conversion between two color spaces, and insertColorSpace which is called * by KoColorSpaceRegistry each time a new color space is added to the registry. * * This class is not part of public API, and can be changed without notice. */ class KRITAPIGMENT_EXPORT KoColorConversionSystem { +public: + struct RegistryInterface { + virtual ~RegistryInterface() {} + + virtual const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName) = 0; + virtual const KoColorSpaceFactory* colorSpaceFactory(const QString &colorModelId, const QString &colorDepthId) const = 0; + virtual QList profilesFor(const KoColorSpaceFactory * csf) const = 0; + virtual QList colorSpacesFor(const KoColorProfile* profile) const = 0; + }; + public: struct Node; struct Vertex; struct NodeKey; friend uint qHash(const KoColorConversionSystem::NodeKey &key); struct Path; /** * Construct a Color Conversion System, leave to the KoColorSpaceRegistry to * create it. */ - KoColorConversionSystem(); + KoColorConversionSystem(RegistryInterface *registryInterface); ~KoColorConversionSystem(); /** * This function is called by the KoColorSpaceRegistry to add a new color space * to the graph of transformation. */ void insertColorSpace(const KoColorSpaceFactory*); void insertColorProfile(const KoColorProfile*); /** * This function is called by the color space to create a color conversion * between two color space. This function search in the graph of transformations * the best possible path between the two color space. */ KoColorConversionTransformation* createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * This function creates two transformations, one from the color space and one to the * color space. The destination color space is picked from a list of color space, such * as the conversion between the two color space is of the best quality. * * The typical use case of this function is for KoColorTransformationFactory which * doesn't support all color spaces, so unsupported color space have to find an * acceptable conversion in order to use that KoColorTransformationFactory. * * @param colorSpace the source color space * @param possibilities a list of color space among which we need to find the best * conversion * @param fromCS the conversion from the source color space will be affected to this * variable * @param toCS the revert conversion to the source color space will be affected to this * variable */ void createColorConverters(const KoColorSpace* colorSpace, const QList< QPair >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const; public: /** * This function return a text that can be compiled using dot to display * the graph of color conversion connection. */ QString toDot() const; /** * This function return a text that can be compiled using dot to display * the graph of color conversion connection, with a red link to show the * path of the best color conversion. */ QString bestPathToDot(const QString& srcKey, const QString& dstKey) const; public: /** * @return true if there is a path between two color spaces */ bool existsPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const; /** * @return true if there is a good path between two color spaces */ bool existsGoodPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const; private: QString vertexToDot(Vertex* v, const QString &options) const; private: /** * Insert an engine. */ Node* insertEngine(const KoColorSpaceEngine* engine); KoColorConversionTransformation* createTransformationFromPath(const KoColorConversionSystem::Path& path, const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Query the registry to get the color space associated with this * node. (default profile) */ const KoColorSpace* defaultColorSpaceForNode(const Node* node) const; /** * Create a new node */ Node* createNode(const QString& _modelId, const QString& _depthId, const QString& _profileName); /** * Initialise a node for ICC color spaces */ void connectToEngine(Node* _node, Node* _engine); const Node* nodeFor(const KoColorSpace*) const; /** * @return the node corresponding to that key, or create it if needed */ Node* nodeFor(const NodeKey& key); const Node* nodeFor(const NodeKey& key) const; /** * @return the list of nodes that correspond to a given model and depth. */ QList nodesFor(const QString& _modelId, const QString& _depthId); /** * @return the node associated with that key, and create it if needed */ Node* nodeFor(const QString& colorModelId, const QString& colorDepthId, const QString& _profileName); const Node* nodeFor(const QString& colorModelId, const QString& colorDepthId, const QString& _profileName) const; /** * @return the vertex between two nodes, or null if the vertex doesn't exist */ Vertex* vertexBetween(Node* srcNode, Node* dstNode); /** * create a vertex between two nodes and return it. */ Vertex* createVertex(Node* srcNode, Node* dstNode); /** * looks for the best path between two nodes */ Path findBestPath(const Node* srcNode, const Node* dstNode) const; /** * Delete all the paths of the list given in argument. */ void deletePaths(QList paths) const; /** * Don't call that function, but raher findBestPath * @internal */ inline Path findBestPathImpl2(const Node* srcNode, const Node* dstNode, bool ignoreHdr, bool ignoreColorCorrectness) const; /** * Don't call that function, but raher findBestPath * @internal */ inline Path findBestPathImpl(const Node* srcNode, const Node* dstNode, bool ignoreHdr) const; private: struct Private; Private* const d; }; #endif diff --git a/libs/pigment/KoColorConversionSystem_p.h b/libs/pigment/KoColorConversionSystem_p.h index e6536bf18a..5d21f930a7 100644 --- a/libs/pigment/KoColorConversionSystem_p.h +++ b/libs/pigment/KoColorConversionSystem_p.h @@ -1,324 +1,327 @@ /* * Copyright (c) 2007-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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORCONVERSIONSYSTEM_P_H #define KOCOLORCONVERSIONSYSTEM_P_H #include "DebugPigment.h" #include "KoColorSpaceRegistry.h" #include "KoColorModelStandardIds.h" #include "KoColorConversionTransformationFactory.h" #include "KoColorSpaceEngine.h" #include struct KoColorConversionSystem::Node { Node() : isHdr(false) , isInitialized(false) , referenceDepth(0) , isGray(false) , crossingCost(1) , colorSpaceFactory(0) , isEngine(false) , engine(0) {} void init(const KoColorSpaceFactory* _colorSpaceFactory) { dbgPigment << "Initialise " << modelId << " " << depthId << " " << profileName; if (isInitialized) { dbgPigment << "Re-initializing node. Old factory" << colorSpaceFactory << "new factory" << _colorSpaceFactory; } isInitialized = true; if (_colorSpaceFactory) { isHdr = _colorSpaceFactory->isHdr(); colorSpaceFactory = _colorSpaceFactory; referenceDepth = _colorSpaceFactory->referenceDepth(); isGray = (_colorSpaceFactory->colorModelId() == GrayAColorModelID || _colorSpaceFactory->colorModelId() == GrayColorModelID || _colorSpaceFactory->colorModelId() == AlphaColorModelID); } } void init(const KoColorSpaceEngine* _engine) { Q_ASSERT(!isInitialized); isEngine = true; isInitialized = true; isHdr = true; engine = _engine; } QString id() const { return modelId + " " + depthId + " " + profileName; } QString modelId; QString depthId; QString profileName; bool isHdr; bool isInitialized; int referenceDepth; QList outputVertexes; bool isGray; int crossingCost; const KoColorSpaceFactory* colorSpaceFactory; bool isEngine; const KoColorSpaceEngine* engine; }; Q_DECLARE_TYPEINFO(KoColorConversionSystem::Node, Q_MOVABLE_TYPE); struct KoColorConversionSystem::Vertex { Vertex(Node* _srcNode, Node* _dstNode) : srcNode(_srcNode) , dstNode(_dstNode) , factoryFromSrc(0) , factoryFromDst(0) { } ~Vertex() { if (factoryFromSrc == factoryFromDst) { delete factoryFromSrc; } else { delete factoryFromSrc; delete factoryFromDst; } } void setFactoryFromSrc(KoColorConversionTransformationFactory* factory) { factoryFromSrc = factory; initParameter(factoryFromSrc); } void setFactoryFromDst(KoColorConversionTransformationFactory* factory) { factoryFromDst = factory; if (!factoryFromSrc) initParameter(factoryFromDst); } void initParameter(KoColorConversionTransformationFactory* transfo) { conserveColorInformation = transfo->conserveColorInformation(); conserveDynamicRange = transfo->conserveDynamicRange(); } KoColorConversionTransformationFactory* factory() { if (factoryFromSrc) return factoryFromSrc; return factoryFromDst; } Node* srcNode; Node* dstNode; bool conserveColorInformation; bool conserveDynamicRange; private: KoColorConversionTransformationFactory* factoryFromSrc; // Factory provided by the destination node KoColorConversionTransformationFactory* factoryFromDst; // Factory provided by the destination node }; struct KoColorConversionSystem::NodeKey { NodeKey(const QString &_modelId, const QString &_depthId, const QString &_profileName) : modelId(_modelId) , depthId(_depthId) , profileName(_profileName) {} bool operator==(const KoColorConversionSystem::NodeKey& rhs) const { return modelId == rhs.modelId && depthId == rhs.depthId && profileName == rhs.profileName; } QString modelId; QString depthId; QString profileName; }; Q_DECLARE_TYPEINFO(KoColorConversionSystem::NodeKey, Q_MOVABLE_TYPE); struct KoColorConversionSystem::Path { Path() : respectColorCorrectness(true) , referenceDepth(0) , keepDynamicRange(true) , isGood(false) , cost(0) {} Node* startNode() { return (vertexes.first())->srcNode; } bool operator==(const Path &other) const { return other.vertexes == vertexes; } const Node* startNode() const { return (vertexes.first())->srcNode; } Node* endNode() { return (vertexes.last())->dstNode; } const Node* endNode() const { return (vertexes.last())->dstNode; } bool isEmpty() const { return vertexes.isEmpty(); } void appendVertex(Vertex* v) { if (vertexes.empty()) { referenceDepth = v->srcNode->referenceDepth; } vertexes.append(v); if (!v->conserveColorInformation) respectColorCorrectness = false; if (!v->conserveDynamicRange) keepDynamicRange = false; referenceDepth = qMin(referenceDepth, v->dstNode->referenceDepth); cost += v->dstNode->crossingCost; } // Compress path to hide the Engine node and correctly select the factory typedef QPair node2factory; QList< node2factory > compressedPath() const { QList< node2factory > nodes; nodes.push_back(node2factory(vertexes.first()->srcNode , vertexes.first()->factory())); const KoColorConversionTransformationAbstractFactory* previousFactory = 0; Q_FOREACH (Vertex* vertex, vertexes) { // Unless the node is the icc node, add it to the path Node* n = vertex->dstNode; if (n->isEngine) { previousFactory = n->engine; } else { nodes.push_back( node2factory(n, previousFactory ? previousFactory : vertex->factory())); previousFactory = 0; } } return nodes; } int length() const { return vertexes.size(); } bool contains(Node* n) const { Q_FOREACH (Vertex* v, vertexes) { if (v->srcNode == n || v->dstNode == n) { return true; } } return false; } QList vertexes; bool respectColorCorrectness; int referenceDepth; bool keepDynamicRange; bool isGood; int cost; }; Q_DECLARE_TYPEINFO(KoColorConversionSystem::Path, Q_MOVABLE_TYPE); inline QDebug operator<<(QDebug dbg, const KoColorConversionSystem::Path &path) { bool havePrintedFirst = false; Q_FOREACH (const KoColorConversionSystem::Vertex *v, path.vertexes) { if (!havePrintedFirst) { dbg.nospace() << v->srcNode->id(); havePrintedFirst = true; } dbg.nospace() << "->" << v->dstNode->id(); } return dbg.space(); } typedef QHash Node2PathHash; uint qHash(const KoColorConversionSystem::NodeKey &key) { return qHash(key.modelId) + qHash(key.depthId); } struct Q_DECL_HIDDEN KoColorConversionSystem::Private { + Private(RegistryInterface *_registryInterface) : registryInterface(_registryInterface) {} + QHash graph; QList vertexes; + RegistryInterface *registryInterface; }; #define CHECK_ONE_AND_NOT_THE_OTHER(name) \ if(path1. name && !path2. name) \ { \ return true; \ } \ if(!path1. name && path2. name) \ { \ return false; \ } struct PathQualityChecker { PathQualityChecker(int _referenceDepth, bool _ignoreHdr, bool _ignoreColorCorrectness) : referenceDepth(_referenceDepth) , ignoreHdr(_ignoreHdr) , ignoreColorCorrectness(_ignoreColorCorrectness) {} /// @return true if the path maximize all the criterions (except length) inline bool isGoodPath(const KoColorConversionSystem::Path & path) const { return (path.respectColorCorrectness || ignoreColorCorrectness) && (path.referenceDepth >= referenceDepth) && (path.keepDynamicRange || ignoreHdr); } /** * Compare two paths. */ inline bool lessWorseThan(const KoColorConversionSystem::Path &path1, const KoColorConversionSystem::Path &path2) const { // There is no point in comparing two paths which doesn't start from the same node or doesn't end at the same node if (!ignoreHdr) { CHECK_ONE_AND_NOT_THE_OTHER(keepDynamicRange) } if (!ignoreColorCorrectness) { CHECK_ONE_AND_NOT_THE_OTHER(respectColorCorrectness) } if (path1.referenceDepth == path2.referenceDepth) { return path1.cost < path2.cost; // if they have the same cost, well anyway you have to choose one, and there is no point in keeping one and not the other } return path1.referenceDepth > path2.referenceDepth; } int referenceDepth; bool ignoreHdr; bool ignoreColorCorrectness; }; #undef CHECK_ONE_AND_NOT_THE_OTHER #endif diff --git a/libs/pigment/KoColorProfileStorage.cpp b/libs/pigment/KoColorProfileStorage.cpp new file mode 100644 index 0000000000..0f91923e63 --- /dev/null +++ b/libs/pigment/KoColorProfileStorage.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2017 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KoColorProfileStorage.h" + +#include +#include +#include + +#include "KoColorSpaceFactory.h" +#include "KoColorProfile.h" +#include "kis_assert.h" + + +struct KoColorProfileStorage::Private { + QHash profileMap; + QHash profileUniqueIdMap; + QHash profileAlias; + QReadWriteLock lock; + + void populateUniqueIdMap(); +}; + +KoColorProfileStorage::KoColorProfileStorage() + : d(new Private) +{ + +} + +KoColorProfileStorage::~KoColorProfileStorage() +{ +} + +void KoColorProfileStorage::addProfile(KoColorProfile *profile) +{ + QWriteLocker locker(&d->lock); + + if (profile->valid()) { + d->profileMap[profile->name()] = profile; + if (!d->profileUniqueIdMap.isEmpty()) { + d->profileUniqueIdMap.insert(profile->uniqueId(), profile); + } + } +} + +void KoColorProfileStorage::addProfile(const KoColorProfile *profile) +{ + addProfile(profile->clone()); +} + +void KoColorProfileStorage::removeProfile(KoColorProfile *profile) +{ + QWriteLocker locker(&d->lock); + + d->profileMap.remove(profile->name()); + if (!d->profileUniqueIdMap.isEmpty()) { + d->profileUniqueIdMap.remove(profile->uniqueId()); + } +} + +bool KoColorProfileStorage::containsProfile(const KoColorProfile *profile) +{ + QReadLocker l(&d->lock); + return d->profileMap.contains(profile->name()); +} + +void KoColorProfileStorage::addProfileAlias(const QString &name, const QString &to) +{ + QWriteLocker l(&d->lock); + d->profileAlias[name] = to; +} + +QString KoColorProfileStorage::profileAlias(const QString &name) const +{ + QReadLocker l(&d->lock); + return d->profileAlias.value(name, name); +} + +const KoColorProfile *KoColorProfileStorage::profileByName(const QString &name) const +{ + QReadLocker l(&d->lock); + return d->profileMap.value(d->profileAlias.value(name, name), 0); +} + +void KoColorProfileStorage::Private::populateUniqueIdMap() +{ + QWriteLocker l(&lock); + profileUniqueIdMap.clear(); + + for (auto it = profileMap.constBegin(); + it != profileMap.constEnd(); + ++it) { + + KoColorProfile *profile = it.value(); + QByteArray id = profile->uniqueId(); + + if (!id.isEmpty()) { + profileUniqueIdMap.insert(id, profile); + } + } +} + + +const KoColorProfile *KoColorProfileStorage::profileByUniqueId(const QByteArray &id) const +{ + QReadLocker l(&d->lock); + if (d->profileUniqueIdMap.isEmpty()) { + l.unlock(); + d->populateUniqueIdMap(); + l.relock(); + } + return d->profileUniqueIdMap.value(id, 0); + +} + +QList KoColorProfileStorage::profilesFor(const KoColorSpaceFactory *csf) const +{ + QList profiles; + if (!csf) return profiles; + + QReadLocker l(&d->lock); + + QHash::ConstIterator it; + for (it = d->profileMap.constBegin(); it != d->profileMap.constEnd(); ++it) { + KoColorProfile * profile = it.value(); + if (csf->profileIsCompatible(profile)) { + Q_ASSERT(profile); + // if (profile->colorSpaceSignature() == csf->colorSpaceSignature()) { + profiles.push_back(profile); + } + } + return profiles; +} diff --git a/libs/pigment/KoColorProfileStorage.h b/libs/pigment/KoColorProfileStorage.h new file mode 100644 index 0000000000..2e1406bfe5 --- /dev/null +++ b/libs/pigment/KoColorProfileStorage.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KOCOLORPROFILESTORAGE_H +#define KOCOLORPROFILESTORAGE_H + +#include + +class QByteArray; +class QString; +class KoColorProfile; +class KoColorSpaceFactory; + +/** + * @brief The KoColorProfileStorage class is a "composite subclass" of + * KoColorSpaceRegistry that ensures that the access to profiles is guarded + * by a separate lock and the hierarchy of locks is always followed (which + * avoid deadlocks). + * + * Registry locking hierarchy is basically the following: + * + * 1) KoColorSpaceRegistry::Private::registrylock + * 2) KoColorProfileStorage::Private::lock + * + * It means that we can take any single lock if we need it separately, but + * if we need to take both of them, we should always take them is a specified + * order. + * + * Encapsulation of the profile accesses inside a separate class lets us + * follow this rule without even thinking of it. KoColorProfileStorage just + * *never* calls any method of the registry, therefore lock order inverion is + * not possible, + */ +class KoColorProfileStorage +{ +public: + KoColorProfileStorage(); + ~KoColorProfileStorage(); + + /** + * Register the profile in the storage + * @param profile the new profile to be registered + */ + void addProfile(KoColorProfile* profile); + void addProfile(const KoColorProfile* profile); // TODO + + /** + * Removes the profile from the storage. + * Please note that the caller should delete the profile object himself! + * + * @param profile the profile to be removed + */ + void removeProfile(KoColorProfile* profile); + + /** + * @brief containsProfile shows if a profile is registered in teh storage + */ + bool containsProfile(const KoColorProfile *profile); + + /** + * Create an alias to a profile with a different name. Then @ref profileByName + * will return the profile @p to when passed @p name as a parameter. + */ + void addProfileAlias(const QString& name, const QString& to); + + /** + * @return the profile alias, or name if not aliased + */ + QString profileAlias(const QString& name) const; + + /** + * Return a profile by its given name, or 0 if none registered. + * @return a profile by its given name, or 0 if none registered. + * @param name the product name as set on the profile. + * @see addProfile() + * @see KoColorProfile::productName() + */ + const KoColorProfile * profileByName(const QString & name) const ; + + + /** + * Returns a profile by its unique id stored/calculated in the header. + * The first call to this function might take long, because the map is + * created on the first use only (atm used by SVG only) + * @param id unique ProfileID of the profile (MD5 sum of its header) + * @return the profile or 0 if not found + */ + const KoColorProfile *profileByUniqueId(const QByteArray &id) const; + + /** + * Return the list of profiles for a colorspace represented by its factory. + * Profiles will not work with any color space, you can query which profiles + * that are registered with this registry can be used in combination with the + * argument factory. + * @param csf is a factory for the requested color space + * @return a list of profiles for the factory + */ + QList profilesFor(const KoColorSpaceFactory * csf) const; + +private: + struct Private; + const QScopedPointer d; +}; + +#endif // KOCOLORPROFILESTORAGE_H diff --git a/libs/pigment/KoColorSpace.cpp b/libs/pigment/KoColorSpace.cpp index 634bbdb533..bb9cdb8ce6 100644 --- a/libs/pigment/KoColorSpace.cpp +++ b/libs/pigment/KoColorSpace.cpp @@ -1,821 +1,819 @@ /* * Copyright (c) 2005 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorSpace.h" #include "KoColorSpace_p.h" #include "KoChannelInfo.h" #include "DebugPigment.h" #include "KoCompositeOp.h" #include "KoColorTransformation.h" #include "KoColorTransformationFactory.h" #include "KoColorTransformationFactoryRegistry.h" #include "KoColorConversionCache.h" #include "KoColorConversionSystem.h" #include "KoColorSpaceRegistry.h" #include "KoColorProfile.h" #include "KoCopyColorConversionTransformation.h" #include "KoFallBackColorTransformation.h" #include "KoUniqueNumberForIdServer.h" #include "KoMixColorsOp.h" #include "KoConvolutionOp.h" #include "KoCompositeOpRegistry.h" #include "KoColorSpaceEngine.h" #include #include #include #include #include #include KoColorSpace::KoColorSpace() : d(new Private()) { } KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp) : d(new Private()) { d->id = id; d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id); d->name = name; d->mixColorsOp = mixColorsOp; d->convolutionOp = convolutionOp; d->transfoToRGBA16 = 0; d->transfoFromRGBA16 = 0; d->transfoToLABA16 = 0; d->transfoFromLABA16 = 0; d->gamutXYY = QPolygonF(); d->TRCXYY = QPolygonF(); d->colorants = QVector (0); d->lumaCoefficients = QVector (0); d->iccEngine = 0; d->deletability = NotOwnedByRegistry; } KoColorSpace::~KoColorSpace() { Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete); qDeleteAll(d->compositeOps); Q_FOREACH (KoChannelInfo * channel, d->channels) { delete channel; } if (d->deletability == NotOwnedByRegistry) { KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache(); if (cache) { cache->colorSpaceIsDestroyed(this); } } delete d->mixColorsOp; delete d->convolutionOp; delete d->transfoToRGBA16; delete d->transfoFromRGBA16; delete d->transfoToLABA16; delete d->transfoFromLABA16; delete d; } bool KoColorSpace::operator==(const KoColorSpace& rhs) const { const KoColorProfile* p1 = rhs.profile(); const KoColorProfile* p2 = profile(); return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2)); } QString KoColorSpace::id() const { return d->id; } QString KoColorSpace::name() const { return d->name; } //Color space info stuff. QPolygonF KoColorSpace::gamutXYY() const { if (d->gamutXYY.empty()) { //now, let's decide on the boundary. This is a bit tricky because icc profiles can be both matrix-shaper and cLUT at once if the maker so pleases. //first make a list of colors. qreal max = 1.0; if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") { //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general. max = this->channels()[0]->getUIMax(); } int samples = 5;//amount of samples in our color space. - QString name = KoColorSpaceRegistry::instance()->colorSpaceFactory("XYZAF32")->defaultProfile(); - const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32", name); + const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32"); quint8 *data = new quint8[pixelSize()]; quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel. //QVector sampleCoordinates(pow(colorChannelCount(),samples)); //sampleCoordinates.fill(0.0); // This is fixed to 5 since the maximum number of channels are 5 for CMYKA QVector channelValuesF(5);//for getting the coordinates. for(int x=0;xnormalisedChannelsValue(data2, channelValuesF); qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->gamutXYY << QPointF(x,y); } else { for(int y=0;ynormalisedChannelsValue(data2, channelValuesF); qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]); qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]); d->gamutXYY<< QPointF(x,y); } } else { channelValuesF[0]=(max/(samples-1))*(x); channelValuesF[1]=(max/(samples-1))*(y); channelValuesF[2]=(max/(samples-1))*(z); channelValuesF[3]=max; if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz. fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); } qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->gamutXYY<< QPointF(x,y); } } } } } delete[] data; //if we ever implement a boundary-checking thing I'd add it here. return d->gamutXYY; } else { return d->gamutXYY; } } QPolygonF KoColorSpace::estimatedTRCXYY() const { if (d->TRCXYY.empty()){ qreal max = 1.0; if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") { //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general. max = this->channels()[0]->getUIMax(); } - QString name = KoColorSpaceRegistry::instance()->colorSpaceFactory("XYZAF16")->defaultProfile(); - const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F16", name); + const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F16"); quint8 *data = new quint8[pixelSize()]; quint8 data2[8]; // xyza is 8 bytes per pixel. // This is fixed to 5 since the maximum number of channels are 5 for CMYKA QVector channelValuesF(5);//for getting the coordinates. for (quint32 i=0; i0; j--){ channelValuesF.fill(0.0); channelValuesF[i] = ((max/4)*(5-j)); if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz. fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); } if (j==0) { colorantY = channelValuesF[1]; if (d->colorants.size()<2){ d->colorants.resize(3*colorChannelCount()); d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+2]= channelValuesF[1]; } } d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(5-j))); } } else { for (int j=0; j<5; j++){ channelValuesF.fill(0.0); channelValuesF[i] = ((max/4)*(j)); fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); if (j==0) { colorantY = channelValuesF[1]; if (d->colorants.size()<2){ d->colorants.resize(3*colorChannelCount()); d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+2]= channelValuesF[1]; } } d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(j))); } } } delete[] data; return d->TRCXYY; } else { return d->TRCXYY; } } QVector KoColorSpace::colorants() const { if (d->colorants.size()>1){ return d->colorants; } else if (profile() && profile()->hasColorants()) { d->colorants.resize(3*colorChannelCount()); d->colorants = profile()->getColorantsxyY(); return d->colorants; } else { estimatedTRCXYY(); return d->colorants; } } QVector KoColorSpace::lumaCoefficients() const { if (d->lumaCoefficients.size()>1){ return d->lumaCoefficients; } else { d->lumaCoefficients.resize(3); if (colorModelId().id()!="RGBA") { d->lumaCoefficients.fill(0.33); } else { colorants(); if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) { d->lumaCoefficients[0]=0.2126; d->lumaCoefficients[1]=0.7152; d->lumaCoefficients[2]=0.0722; } else { d->lumaCoefficients[0]=d->colorants[2]; d->lumaCoefficients[1]=d->colorants[5]; d->lumaCoefficients[2]=d->colorants[8]; } } return d->lumaCoefficients; } } QList KoColorSpace::channels() const { return d->channels; } QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const { QBitArray ba(d->channels.size()); if (!color && !alpha) return ba; for (int i = 0; i < d->channels.size(); ++i) { KoChannelInfo * channel = d->channels.at(i); if ((color && channel->channelType() == KoChannelInfo::COLOR) || (alpha && channel->channelType() == KoChannelInfo::ALPHA)) ba.setBit(i, true); } return ba; } void KoColorSpace::addChannel(KoChannelInfo * ci) { d->channels.push_back(ci); } bool KoColorSpace::hasCompositeOp(const QString& id) const { return d->compositeOps.contains(id); } QList KoColorSpace::compositeOps() const { return d->compositeOps.values(); } KoMixColorsOp* KoColorSpace::mixColorsOp() const { return d->mixColorsOp; } KoConvolutionOp* KoColorSpace::convolutionOp() const { return d->convolutionOp; } const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const { const QHash::ConstIterator it = d->compositeOps.constFind(id); if (it != d->compositeOps.constEnd()) { return it.value(); } else { warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER; return d->compositeOps.value(COMPOSITE_OVER); } } void KoColorSpace::addCompositeOp(const KoCompositeOp * op) { if (op->colorSpace()->id() == id()) { d->compositeOps.insert(op->id(), const_cast(op)); } } const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const { if (!d->transfoToLABA16) { - d->transfoToLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; + d->transfoToLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToLABA16; } const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const { if (!d->transfoFromLABA16) { - d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; + d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromLABA16; } const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const { if (!d->transfoToRGBA16) { - d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; + d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToRGBA16; } const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const { if (!d->transfoFromRGBA16) { - d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16() , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; + d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16() , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromRGBA16; } void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toRgbA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromRgbA16Converter()->transform(src, dst, nPixels); } KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { return new KoCopyColorConversionTransformation(this); } else { - return KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags); + return KoColorSpaceRegistry::instance()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags); } } bool KoColorSpace::convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { if (src != dst) { memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize()); } } else { KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags); cct.transformation()->transform(src, dst, numPixels); } return true; } KoColorConversionTransformation * KoColorSpace::createProofingTransform(const KoColorSpace *dstColorSpace, const KoColorSpace *proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const { if (!d->iccEngine) { d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); } if (!d->iccEngine) return 0; return d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning, adaptationState); } bool KoColorSpace::proofPixelsTo(const quint8 *src, quint8 *dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const { proofingTransform->transform(src, dst, numPixels); //the transform is deleted in the destructor. return true; } void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT_X(*op->colorSpace() == *this, "KoColorSpace::bitBlt", QString("Composite op is for color space %1 (%2) while this is %3 (%4)").arg(op->colorSpace()->id()).arg(op->colorSpace()->profile()->name()).arg(id()).arg(profile()->name()).toLatin1()); if(params.rows <= 0 || params.cols <= 0) return; if(!(*this == *srcSpace)) { if (preferCompositionInSourceColorSpace() && srcSpace->hasCompositeOp(op->id())) { quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize(); QVector * conversionDstCache = threadLocalConversionCache(params.rows * conversionDstBufferStride); quint8* conversionDstData = conversionDstCache->data(); for(qint32 row=0; rowcompositeOp(op->id()); KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.dstRowStart = conversionDstData; paramInfo.dstRowStride = conversionDstBufferStride; otherOp->composite(paramInfo); for(qint32 row=0; rowconvertPixelsTo(conversionDstData + row * conversionDstBufferStride, params.dstRowStart + row * params.dstRowStride, this, params.cols, renderingIntent, conversionFlags); } } else { quint32 conversionBufferStride = params.cols * pixelSize(); QVector * conversionCache = threadLocalConversionCache(params.rows * conversionBufferStride); quint8* conversionData = conversionCache->data(); for(qint32 row=0; rowconvertPixelsTo(params.srcRowStart + row * params.srcRowStride, conversionData + row * conversionBufferStride, this, params.cols, renderingIntent, conversionFlags); } KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.srcRowStart = conversionData; paramInfo.srcRowStride = conversionBufferStride; op->composite(paramInfo); } } else { op->composite(params); } } QVector * KoColorSpace::threadLocalConversionCache(quint32 size) const { QVector * ba = 0; if (!d->conversionCache.hasLocalData()) { ba = new QVector(size, '0'); d->conversionCache.setLocalData(ba); } else { ba = d->conversionCache.localData(); if ((quint8)ba->size() < size) ba->resize(size); } return ba; } KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash & parameters) const { KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id); if (!factory) return 0; QPair model(colorModelId(), colorDepthId()); QList< QPair > models = factory->supportedModels(); if (models.isEmpty() || models.contains(model)) { return factory->createTransformation(this, parameters); } else { // Find the best solution // TODO use the color conversion cache KoColorConversionTransformation* csToFallBack = 0; KoColorConversionTransformation* fallBackToCs = 0; - KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverters(this, models, csToFallBack, fallBackToCs); + KoColorSpaceRegistry::instance()->createColorConverters(this, models, csToFallBack, fallBackToCs); Q_ASSERT(csToFallBack); Q_ASSERT(fallBackToCs); KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters); return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo); } } void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{ int channelnumber = channelCount(); QVector channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ihasTRC()){ //only linearise and crunch the luma if there's a TRC profile()->linearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = pow(luma, 1/2.2); luma = qMin(1.0, luma + step); luma = pow(luma, 2.2); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); } else { qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = qMin(1.0, luma + step); channelValues = fromHSY(&hue, &sat, &luma); } for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ihasTRC()){ //only linearise and crunch the luma if there's a TRC profile()->linearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = pow(luma, 1/2.2); if (luma-step<0.0) { luma=0.0; } else { luma -= step; } luma = pow(luma, 2.2); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); } else { qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (luma-step<0.0) { luma=0.0; } else { luma -= step; } channelValues = fromHSY(&hue, &sat, &luma); } for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); sat += step; sat = qBound(0.0, sat, 1.0); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); sat -= step; sat = qBound(0.0, sat, 1.0); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (hue+step>1.0){ hue=(hue+step)- 1.0; } else { hue += step; } channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (hue-step<0.0){ hue=1.0-(step-hue); } else { hue -= step; } channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); u += step; u = qBound(0.0, u, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); u -= step; u = qBound(0.0, u, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); v += step; v = qBound(0.0, v, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); v -= step; v = qBound(0.0, v, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;irgb8(dstProfile); if (data) this->convertPixelsTo(const_cast(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags); return img; } bool KoColorSpace::preferCompositionInSourceColorSpace() const { return false; } diff --git a/libs/pigment/KoColorSpaceFactory.cpp b/libs/pigment/KoColorSpaceFactory.cpp index 52632cef92..c71682eacf 100644 --- a/libs/pigment/KoColorSpaceFactory.cpp +++ b/libs/pigment/KoColorSpaceFactory.cpp @@ -1,107 +1,107 @@ /* * Copyright (c) 2010 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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorSpaceFactory.h" #include "DebugPigment.h" #include #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" struct Q_DECL_HIDDEN KoColorSpaceFactory::Private { QList colorprofiles; QList colorspaces; QHash availableColorspaces; QMutex mutex; #ifndef NDEBUG QHash stackInformation; #endif }; KoColorSpaceFactory::KoColorSpaceFactory() : d(new Private) { } KoColorSpaceFactory::~KoColorSpaceFactory() { #ifndef NDEBUG // Check that all color spaces have been released int count = 0; count += d->availableColorspaces.size(); for(QHash::const_iterator it = d->stackInformation.constBegin(); it != d->stackInformation.constEnd(); ++it) { errorPigment << "*******************************************"; errorPigment << it.key()->id() << " still in used, and grabed in: "; errorPigment << it.value(); } if( count != d->colorspaces.size()) { errorPigment << (d->colorspaces.size() - count) << " colorspaces are still used"; } Q_ASSERT(count == d->colorspaces.size()); #endif Q_FOREACH (KoColorSpace* cs, d->colorspaces) { delete cs; } Q_FOREACH (KoColorProfile* profile, d->colorprofiles) { KoColorSpaceRegistry::instance()->removeProfile(profile); delete profile; } delete d; } -const KoColorProfile* KoColorSpaceFactory::colorProfile(const QByteArray& rawData) const +const KoColorProfile *KoColorSpaceFactory::colorProfile(const QByteArray &rawData, KoColorSpaceFactory::ProfileRegistrationInterface *registrationInterface) const { KoColorProfile* colorProfile = createColorProfile(rawData); if (colorProfile && colorProfile->valid()) { - if (const KoColorProfile* existingProfile = KoColorSpaceRegistry::instance()->profileByName(colorProfile->name())) { + if (const KoColorProfile* existingProfile = registrationInterface->profileByName(colorProfile->name())) { delete colorProfile; return existingProfile; } - KoColorSpaceRegistry::instance()->addProfile(colorProfile); + registrationInterface->registerNewProfile(colorProfile); d->colorprofiles.append(colorProfile); } return colorProfile; } const KoColorSpace *KoColorSpaceFactory::grabColorSpace(const KoColorProfile * profile) { QMutexLocker l(&d->mutex); Q_ASSERT(profile); auto it = d->availableColorspaces.find(profile->name()); KoColorSpace* cs; if (it == d->availableColorspaces.end()) { cs = createColorSpace(profile); if (cs) { d->availableColorspaces[profile->name()] = cs; } } else { cs = it.value(); } return cs; } diff --git a/libs/pigment/KoColorSpaceFactory.h b/libs/pigment/KoColorSpaceFactory.h index b04a398563..8d4dac91e3 100644 --- a/libs/pigment/KoColorSpaceFactory.h +++ b/libs/pigment/KoColorSpaceFactory.h @@ -1,140 +1,147 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (c) 2006-2007 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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACEFACTORY_H #define KOCOLORSPACEFACTORY_H #include "KoColorSpaceConstants.h" #include "KoColorConversionTransformation.h" #include #include "kritapigment_export.h" class KoColorProfile; class KoColorConversionTransformationFactory; /** * This class is used to create color spaces. */ class KRITAPIGMENT_EXPORT KoColorSpaceFactory { protected: KoColorSpaceFactory(); public: virtual ~KoColorSpaceFactory(); /** * Return the unchanging name of this color space */ virtual QString id() const = 0; /** * return the i18n'able description. */ virtual QString name() const = 0; /** * @return true if the color space should be shown in a User Interface, or false * other wise. */ virtual bool userVisible() const = 0; /** * @return a string that identify the color model (for instance "RGB" or "CMYK" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorModelId() const = 0; /** * @return a string that identify the bit depth (for instance "U8" or "F16" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorDepthId() const = 0; /** * @param profile a pointer to a color profile * @return true if the color profile can be used by a color space created by * this factory */ virtual bool profileIsCompatible(const KoColorProfile* profile) const = 0; /** * @return the name of the color space engine for this color space, or "" if none */ virtual QString colorSpaceEngine() const = 0; /** * @return true if the color space supports High-Dynamic Range. */ virtual bool isHdr() const = 0; /** * @return the reference depth, that is for a color space where all channels have the same * depth, this is the depth of one channel, for a color space with different bit depth for * each channel, it's usually the highest bit depth. This value is used by the Color * Conversion System to check if a lost of bit depth during a color conversion is * acceptable, for instance when converting from RGB32bit to XYZ16bit, it's acceptable to go * through a conversion to RGB16bit, while it's not the case for RGB32bit to XYZ32bit. */ virtual int referenceDepth() const = 0; /** * @return the list of color conversion provided by this colorspace, the factories * constructed by this functions are owned by the caller of the function */ virtual QList colorConversionLinks() const = 0; /** * @return the cost of the usage of the colorspace in the conversion graph. The higher the cost, * the less probably the color space will be chosen for the conversion. */ virtual int crossingCost() const = 0; /** * Returns the default icc profile for use with this colorspace. This may be "" * * @return the default icc profile name */ virtual QString defaultProfile() const = 0; + struct ProfileRegistrationInterface + { + virtual ~ProfileRegistrationInterface() {} + virtual const KoColorProfile* profileByName(const QString &profileName) const = 0; + virtual void registerNewProfile(KoColorProfile *profile) = 0; + }; + /** * Create a color profile from a memory array, if possible, otherwise return 0. * This will replace the existing profile with the name in the KoColorSpaceRegistry * * This will call the decendant's createColorProfile() */ - const KoColorProfile* colorProfile(const QByteArray& rawData) const; + const KoColorProfile* colorProfile(const QByteArray& rawData, ProfileRegistrationInterface *registrationInterface) const; /** * Create or reuse the existing colorspace for the given profile. * * This will call the decendant's createColorSpace */ const KoColorSpace *grabColorSpace(const KoColorProfile *profile); protected: /** * creates a color space using the given profile. */ virtual KoColorSpace *createColorSpace(const KoColorProfile *) const = 0; virtual KoColorProfile* createColorProfile(const QByteArray& rawData) const = 0; private: struct Private; Private* const d; }; #endif // KOCOLORSPACEFACTORY_H diff --git a/libs/pigment/KoColorSpaceRegistry.cpp b/libs/pigment/KoColorSpaceRegistry.cpp index 2ffa87b175..353e4b3de2 100644 --- a/libs/pigment/KoColorSpaceRegistry.cpp +++ b/libs/pigment/KoColorSpaceRegistry.cpp @@ -1,753 +1,818 @@ /* * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004,2010 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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include "KoPluginLoader.h" #include "KoGenericRegistry.h" #include "DebugPigment.h" #include "KoBasicHistogramProducers.h" #include "KoColorSpace.h" #include "KoColorProfile.h" #include "KoColorConversionCache.h" #include "KoColorConversionSystem.h" #include "colorspaces/KoAlphaColorSpace.h" #include "colorspaces/KoLabColorSpace.h" #include "colorspaces/KoRgbU16ColorSpace.h" #include "colorspaces/KoRgbU8ColorSpace.h" #include "colorspaces/KoSimpleColorSpaceEngine.h" #include "KoColorSpace_p.h" +#include "kis_assert.h" +#include "KoColorProfileStorage.h" +#include + + Q_GLOBAL_STATIC(KoColorSpaceRegistry, s_instance) struct Q_DECL_HIDDEN KoColorSpaceRegistry::Private { + + // interface for KoColorSpaceFactory + struct ProfileRegistrationInterface; + // interface for KoColorConversionSystem + struct ConversionSystemInterface; + + + Private(KoColorSpaceRegistry *_q) : q(_q) {} + + KoColorSpaceRegistry *q; + KoGenericRegistry colorSpaceFactoryRegistry; - QList localFactories; - QHash profileMap; - QHash profileUniqueIdMap; - QHash profileAlias; + KoColorProfileStorage profileStorage; QHash csMap; + QScopedPointer conversionSystemInterface; KoColorConversionSystem *colorConversionSystem; KoColorConversionCache* colorConversionCache; const KoColorSpace *rgbU8sRGB; const KoColorSpace *lab16sLAB; const KoColorSpace *alphaCs; const KoColorSpace *alphaU16Cs; #ifdef HAVE_OPENEXR const KoColorSpace *alphaF16Cs; #endif const KoColorSpace *alphaF32Cs; QReadWriteLock registrylock; - void populateUniqueIdMap(); + /** + * The function checks if a colorspace with a certain id and profile name can be found in the cache + * NOTE: the function doesn't take any lock but it needs to be called inside a d->registryLock + * locked either in read or write. + * @param csId The colorspace id + * @param profileName The colorspace profile name + * @retval KoColorSpace The matching colorspace + * @retval 0 Null pointer if not match + */ + const KoColorSpace* getCachedColorSpaceImpl(const QString & csId, const QString & profileName) const; + + QString idsToCacheName(const QString & csId, const QString & profileName) const; + QString defaultProfileForCsIdImpl(const QString &csID); + const KoColorProfile * profileForCsIdWithFallbackImpl(const QString &csID, const QString &profileName); + QString colorSpaceIdImpl(const QString & colorModelId, const QString & colorDepthId) const; + + const KoColorSpace *lazyCreateColorSpaceImpl(const QString &csID, const KoColorProfile *profile); + + /** + * Return a colorspace that works with the parameter profile. + * @param profileName the name of the KoColorProfile to be combined with the colorspace + * @return the wanted colorspace, or 0 when the cs and profile can not be combined. + */ + template + const KoColorSpace * colorSpace1(const QString &colorSpaceId, const QString &pName = QString()); + + /** + * Return a colorspace that works with the parameter profile. + * @param colorSpaceId the ID string of the colorspace that you want to have returned + * @param profile the profile be combined with the colorspace + * @return the wanted colorspace, or 0 when the cs and profile can not be combined. + */ + const KoColorSpace * colorSpace1(const QString &colorSpaceId, const KoColorProfile *profile); +}; + +struct KoColorSpaceRegistry::Private::ConversionSystemInterface : public KoColorConversionSystem::RegistryInterface +{ + ConversionSystemInterface(KoColorSpaceRegistry *parentRegistry) + : q(parentRegistry) + { + } + + const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName) { + return q->d->colorSpace1(q->d->colorSpaceIdImpl(colorModelId, colorDepthId), profileName); + } + + const KoColorSpaceFactory* colorSpaceFactory(const QString &colorModelId, const QString &colorDepthId) const { + return q->d->colorSpaceFactoryRegistry.get(q->d->colorSpaceIdImpl(colorModelId, colorDepthId)); + } + + QList profilesFor(const KoColorSpaceFactory * csf) const { + return q->d->profileStorage.profilesFor(csf); + } + + QList colorSpacesFor(const KoColorProfile* profile) const { + QList csfs; + Q_FOREACH (KoColorSpaceFactory* csf, q->d->colorSpaceFactoryRegistry.values()) { + if (csf->profileIsCompatible(profile)) { + csfs.push_back(csf); + } + } + return csfs; + } + +private: + KoColorSpaceRegistry *q; }; KoColorSpaceRegistry* KoColorSpaceRegistry::instance() { if (!s_instance.exists()) { s_instance->init(); } return s_instance; } void KoColorSpaceRegistry::init() { d->rgbU8sRGB = 0; d->lab16sLAB = 0; d->alphaCs = 0; d->alphaU16Cs = 0; #ifdef HAVE_OPENEXR d->alphaF16Cs = 0; #endif d->alphaF32Cs = 0; - d->colorConversionSystem = new KoColorConversionSystem; + d->conversionSystemInterface.reset(new Private::ConversionSystemInterface(this)); + d->colorConversionSystem = new KoColorConversionSystem(d->conversionSystemInterface.data()); d->colorConversionCache = new KoColorConversionCache; KoColorSpaceEngineRegistry::instance()->add(new KoSimpleColorSpaceEngine()); addProfile(new KoDummyColorProfile); // Create the built-in colorspaces - d->localFactories + QList localFactories; + localFactories << new KoAlphaColorSpaceFactory() << new KoAlphaU16ColorSpaceFactory() #ifdef HAVE_OPENEXR << new KoAlphaF16ColorSpaceFactory() #endif << new KoAlphaF32ColorSpaceFactory() << new KoLabColorSpaceFactory() << new KoRgbU8ColorSpaceFactory() << new KoRgbU16ColorSpaceFactory(); - Q_FOREACH (KoColorSpaceFactory *factory, d->localFactories) { + Q_FOREACH (KoColorSpaceFactory *factory, localFactories) { add(factory); } KoPluginLoader::PluginsConfig config; config.whiteList = "ColorSpacePlugins"; config.blacklist = "ColorSpacePluginsDisabled"; config.group = "calligra"; KoPluginLoader::instance()->load("Calligra/ColorSpace", "[X-Pigment-PluginVersion] == 28", config); KoPluginLoader::PluginsConfig configExtensions; configExtensions.whiteList = "ColorSpaceExtensionsPlugins"; configExtensions.blacklist = "ColorSpaceExtensionsPluginsDisabled"; configExtensions.group = "calligra"; KoPluginLoader::instance()->load("Calligra/ColorSpaceExtension", "[X-Pigment-PluginVersion] == 28", configExtensions); dbgPigment << "Loaded the following colorspaces:"; Q_FOREACH (const KoID& id, listKeys()) { dbgPigment << "\t" << id.id() << "," << id.name(); } } -KoColorSpaceRegistry::KoColorSpaceRegistry() : d(new Private()) +KoColorSpaceRegistry::KoColorSpaceRegistry() : d(new Private(this)) { d->colorConversionSystem = 0; d->colorConversionCache = 0; } KoColorSpaceRegistry::~KoColorSpaceRegistry() { // Just leak on exit... It's faster. // delete d->colorConversionSystem; // Q_FOREACH (KoColorProfile* profile, d->profileMap) { // delete profile; // } // d->profileMap.clear(); // Q_FOREACH (const KoColorSpace * cs, d->csMap) { // cs->d->deletability = OwnedByRegistryRegistryDeletes; // } // d->csMap.clear(); // // deleting colorspaces calls a function in the cache // delete d->colorConversionCache; // d->colorConversionCache = 0; // // Delete the colorspace factories // qDeleteAll(d->localFactories); delete d; } void KoColorSpaceRegistry::add(KoColorSpaceFactory* item) { - { - QWriteLocker l(&d->registrylock); - d->colorSpaceFactoryRegistry.add(item); - } + QWriteLocker l(&d->registrylock); + d->colorSpaceFactoryRegistry.add(item); d->colorConversionSystem->insertColorSpace(item); } void KoColorSpaceRegistry::remove(KoColorSpaceFactory* item) { - d->registrylock.lockForRead(); + QWriteLocker l(&d->registrylock); + QList toremove; Q_FOREACH (const KoColorSpace * cs, d->csMap) { if (cs->id() == item->id()) { - toremove.push_back(idsToCacheName(cs->id(), cs->profile()->name())); + toremove.push_back(d->idsToCacheName(cs->id(), cs->profile()->name())); cs->d->deletability = OwnedByRegistryRegistryDeletes; } } - d->registrylock.unlock(); - d->registrylock.lockForWrite(); + Q_FOREACH (const QString& id, toremove) { d->csMap.remove(id); // TODO: should not it delete the color space when removing it from the map ? } d->colorSpaceFactoryRegistry.remove(item->id()); - d->registrylock.unlock(); } void KoColorSpaceRegistry::addProfileAlias(const QString& name, const QString& to) { - QWriteLocker l(&d->registrylock); - d->profileAlias[name] = to; + d->profileStorage.addProfileAlias(name, to); } -QString KoColorSpaceRegistry::profileAlias(const QString& _name) const +QString KoColorSpaceRegistry::profileAlias(const QString& name) const { - QReadLocker l(&d->registrylock); - return d->profileAlias.value(_name, _name); + return d->profileStorage.profileAlias(name); } -const KoColorProfile * KoColorSpaceRegistry::profileByName(const QString & _name) const +const KoColorProfile* KoColorSpaceRegistry::profileByName(const QString &name) const { - QReadLocker l(&d->registrylock); - return d->profileMap.value( profileAlias(_name), 0); -} - -void KoColorSpaceRegistry::Private::populateUniqueIdMap() -{ - QWriteLocker l(®istrylock); - profileUniqueIdMap.clear(); - - for (auto it = profileMap.constBegin(); - it != profileMap.constEnd(); - ++it) { - - KoColorProfile *profile = it.value(); - QByteArray id = profile->uniqueId(); - - if (!id.isEmpty()) { - profileUniqueIdMap.insert(id, profile); - } - } + return d->profileStorage.profileByName(name); } const KoColorProfile * KoColorSpaceRegistry::profileByUniqueId(const QByteArray &id) const { - { - QReadLocker l(&d->registrylock); - if (d->profileUniqueIdMap.isEmpty()) { - l.unlock(); - d->populateUniqueIdMap(); - l.relock(); - } - return d->profileUniqueIdMap.value(id, 0); - } + return d->profileStorage.profileByUniqueId(id); } - -QList KoColorSpaceRegistry::profilesFor(const QString &id) const +QList KoColorSpaceRegistry::profilesFor(const QString &csID) const { - return profilesFor(d->colorSpaceFactoryRegistry.value(id)); -} - -const KoColorSpace * KoColorSpaceRegistry::colorSpace(const KoID &csID, const QString & profileName) -{ - return colorSpace(csID.id(), profileName); + QReadLocker l(&d->registrylock); + return d->profileStorage.profilesFor(d->colorSpaceFactoryRegistry.value(csID)); } const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId, const KoColorProfile *profile) { - return colorSpace(colorSpaceId(colorModelId, colorDepthId), profile); + return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId), profile); } const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName) { - return colorSpace(colorSpaceId(colorModelId, colorDepthId), profileName); + return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId), profileName); } -QList KoColorSpaceRegistry::profilesFor(const KoColorSpaceFactory * csf) const +const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId) { - QReadLocker l(&d->registrylock); - QList profiles; - if (csf == 0) - return profiles; - - QHash::Iterator it; - for (it = d->profileMap.begin(); it != d->profileMap.end(); ++it) { - KoColorProfile * profile = it.value(); - if (csf->profileIsCompatible(profile)) { - Q_ASSERT(profile); - // if (profile->colorSpaceSignature() == csf->colorSpaceSignature()) { - profiles.push_back(profile); - } - } - return profiles; + return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId)); } -QList KoColorSpaceRegistry::colorSpacesFor(const KoColorProfile* _profile) const +bool KoColorSpaceRegistry::profileIsCompatible(const KoColorProfile *profile, const QString &colorSpaceId) { QReadLocker l(&d->registrylock); - QList csfs; - Q_FOREACH (KoColorSpaceFactory* csf, d->colorSpaceFactoryRegistry.values()) { - if (csf->profileIsCompatible(_profile)) { - csfs.push_back(csf); - } - } - return csfs; -} + KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(colorSpaceId); -QList KoColorSpaceRegistry::profilesFor(const KoID& id) const -{ - return profilesFor(id.id()); + return csf ? csf->profileIsCompatible(profile) : false; } void KoColorSpaceRegistry::addProfileToMap(KoColorProfile *p) { - Q_ASSERT(p); - if (p->valid()) { - d->profileMap[p->name()] = p; - if (!d->profileUniqueIdMap.isEmpty()) { - d->profileUniqueIdMap.insert(p->uniqueId(), p); - } - } + d->profileStorage.addProfile(p); } void KoColorSpaceRegistry::addProfile(KoColorProfile *p) { - Q_ASSERT(p); + if (!p->valid()) return; + + QWriteLocker locker(&d->registrylock); if (p->valid()) { addProfileToMap(p); d->colorConversionSystem->insertColorProfile(p); } } void KoColorSpaceRegistry::addProfile(const KoColorProfile* profile) { addProfile(profile->clone()); } void KoColorSpaceRegistry::removeProfile(KoColorProfile* profile) { - d->profileMap.remove(profile->name()); - if (!d->profileUniqueIdMap.isEmpty()) { - d->profileUniqueIdMap.remove(profile->uniqueId()); - } + d->profileStorage.removeProfile(profile); + // FIXME: how about removing it from conversion system? } -const KoColorSpace* KoColorSpaceRegistry::getCachedColorSpace(const QString & csID, const QString & profileName) const +const KoColorSpace* KoColorSpaceRegistry::Private::getCachedColorSpaceImpl(const QString & csID, const QString & profileName) const { - auto it = d->csMap.find(idsToCacheName(csID, profileName)); + auto it = csMap.find(idsToCacheName(csID, profileName)); - if (it != d->csMap.end()) { - return it.value(); + if (it != csMap.end()) { + return it.value(); } return 0; } -QString KoColorSpaceRegistry::idsToCacheName(const QString & csID, const QString & profileName) const +QString KoColorSpaceRegistry::Private::idsToCacheName(const QString & csID, const QString & profileName) const { return csID + "" + profileName; } -const KoColorSpaceFactory* KoColorSpaceRegistry::colorSpaceFactory(const QString &colorSpaceId) const +QString KoColorSpaceRegistry::defaultProfileForColorSpace(const QString &colorSpaceId) const { QReadLocker l(&d->registrylock); - return d->colorSpaceFactoryRegistry.get(colorSpaceId); + return d->defaultProfileForCsIdImpl(colorSpaceId); } -const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString &csID, const QString &pName) +KoColorConversionTransformation *KoColorSpaceRegistry::createColorConverter(const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { - QString profileName = pName; + QWriteLocker l(&d->registrylock); + return d->colorConversionSystem->createColorConverter(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); +} - if (profileName.isEmpty()) { - QReadLocker l(&d->registrylock); - KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(csID); +void KoColorSpaceRegistry::createColorConverters(const KoColorSpace *colorSpace, const QList > &possibilities, KoColorConversionTransformation *&fromCS, KoColorConversionTransformation *&toCS) const +{ + QWriteLocker l(&d->registrylock); + d->colorConversionSystem->createColorConverters(colorSpace, possibilities, fromCS, toCS); +} - if (!csf) { - dbgPigmentCSRegistry << "Unknown color space type : " << csID; - return 0; - } +QString KoColorSpaceRegistry::Private::defaultProfileForCsIdImpl(const QString &csID) +{ + QString defaultProfileName; - profileName = csf->defaultProfile(); + KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID); + if (csf) { + defaultProfileName = csf->defaultProfile(); + } else { + dbgPigmentCSRegistry << "Unknown color space type : " << csID; } - if (profileName.isEmpty()) { - return 0; - } + return defaultProfileName; +} - const KoColorSpace *cs = 0; - { - QReadLocker l(&d->registrylock); - cs = getCachedColorSpace(csID, profileName); - } +const KoColorProfile *KoColorSpaceRegistry::Private::profileForCsIdWithFallbackImpl(const QString &csID, const QString &profileName) +{ + const KoColorProfile *profile = 0; - if (!cs) { - KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(csID); + // last attempt at getting a profile, sometimes the default profile, like adobe cmyk isn't available. + profile = profileStorage.profileByName(profileName); + if (!profile) { + dbgPigmentCSRegistry << "Profile not found :" << profileName; - if (!csf) { - dbgPigmentCSRegistry << "Unknown color space type :" << csf; - return 0; - } + // first try: default + profile = profileStorage.profileByName(defaultProfileForCsIdImpl(csID)); - // last attempt at getting a profile, sometimes the default profile, like adobe cmyk isn't available. - const KoColorProfile *p = profileByName(profileName); - if (!p) { - dbgPigmentCSRegistry << "Profile not found :" << profileName; - - /** - * If the requested profile is not available, try fetching the - * default one - */ - profileName = csf->defaultProfile(); - p = profileByName(profileName); - - /** - * If there is no luck, try to fetch the first one - */ - if (!p) { - QList profiles = profilesFor(csID); - if (!profiles.isEmpty()) { - p = profiles[0]; - Q_ASSERT(p); - } + if (!profile) { + // second try: first profile in the list + QList profiles = profileStorage.profilesFor(colorSpaceFactoryRegistry.value(csID)); + if (profiles.isEmpty() || !profiles.first()) { + dbgPigmentCSRegistry << "Couldn't fetch a fallback profile:" << profileName; + return 0; } + + profile = profiles.first(); } + } + + return profile; +} + +const KoColorSpace *KoColorSpaceRegistry::Private::lazyCreateColorSpaceImpl(const QString &csID, const KoColorProfile *profile) +{ + const KoColorSpace *cs = 0; - // We did our best, but still have no profile: and since csf->grabColorSpace - // needs the profile to find the colorspace, we have to give up. - if (!p) { + /* + * We need to check again here, a thread requesting the same colorspace could've added it + * already, in between the read unlock and write lock. + * TODO: We also potentially changed profileName content, which means we maybe are going to + * create a colorspace that's actually in the space registry cache, but currently this might + * not be an issue because the colorspace should be cached also by the factory, so it won't + * create a new instance. That being said, having two caches with the same stuff doesn't make + * much sense. + */ + cs = getCachedColorSpaceImpl(csID, profile->name()); + if (!cs) { + KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID); + cs = csf->grabColorSpace(profile); + if (!cs) { + dbgPigmentCSRegistry << "Unable to create color space"; return 0; } - profileName = p->name(); - - QWriteLocker l(&d->registrylock); - /* - * We need to check again here, a thread requesting the same colorspace could've added it - * already, in between the read unlock and write lock. - * TODO: We also potentially changed profileName content, which means we maybe are going to - * create a colorspace that's actually in the space registry cache, but currently this might - * not be an issue because the colorspace should be cached also by the factory, so it won't - * create a new instance. That being said, having two caches with the same stuff doesn't make - * much sense. - */ - cs = getCachedColorSpace(csID, profileName); - if (!cs) { - cs = csf->grabColorSpace(p); - if (!cs) { - dbgPigmentCSRegistry << "Unable to create color space"; - return 0; - } - dbgPigmentCSRegistry << "colorspace count: " << d->csMap.count() - << ", adding name: " << idsToCacheName(cs->id(), cs->profile()->name()) - << "\n\tcsID" << csID - << "\n\tprofileName" << profileName - << "\n\tcs->id()" << cs->id() - << "\n\tcs->profile()->name()" << cs->profile()->name() - << "\n\tpName" << pName; - Q_ASSERT(cs->id() == csID); - Q_ASSERT(cs->profile()->name() == profileName); - d->csMap[idsToCacheName(cs->id(), cs->profile()->name())] = cs; - cs->d->deletability = OwnedByRegistryDoNotDelete; + dbgPigmentCSRegistry << "colorspace count: " << csMap.count() + << ", adding name: " << idsToCacheName(cs->id(), cs->profile()->name()) + << "\n\tcsID" << csID + << "\n\tcs->id()" << cs->id() + << "\n\tcs->profile()->name()" << cs->profile()->name() + << "\n\tprofile->name()" << profile->name(); + Q_ASSERT(cs->id() == csID); + Q_ASSERT(cs->profile()->name() == profile->name()); + csMap[idsToCacheName(cs->id(), cs->profile()->name())] = cs; + cs->d->deletability = OwnedByRegistryDoNotDelete; + } + + return cs; +} + +template +const KoColorSpace * KoColorSpaceRegistry::Private::colorSpace1(const QString &csID, const QString &pName) +{ + QString profileName = pName; + + const KoColorSpace *cs = 0; + + { + typename LockPolicy::ReadLocker l(®istrylock); + + if (profileName.isEmpty()) { + profileName = defaultProfileForCsIdImpl(csID); + if (profileName.isEmpty()) return 0; } + + // quick attempt to fetch a cached color space + cs = getCachedColorSpaceImpl(csID, profileName); + } + + if (!cs) { + // slow attemt to create a color space + typename LockPolicy::WriteLocker l(®istrylock); + + const KoColorProfile *profile = + profileForCsIdWithFallbackImpl(csID, profileName); + + // until kis_asert.h is not available in 3.1, use this combo + Q_ASSERT(profile); + if (!profile) return 0; + + cs = lazyCreateColorSpaceImpl(csID, profile); } else { - Q_ASSERT(cs->id() == csID); - Q_ASSERT(cs->profile()->name() == profileName); + KIS_SAFE_ASSERT_RECOVER_NOOP(cs->id() == csID); + KIS_SAFE_ASSERT_RECOVER_NOOP(cs->profile()->name() == profileName); } return cs; } -const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString &csID, const KoColorProfile *profile) +const KoColorSpace * KoColorSpaceRegistry::Private::colorSpace1(const QString &csID, const KoColorProfile *profile) { if (csID.isEmpty()) { return 0; + } else if (!profile) { + return colorSpace1(csID); } - if (profile) { - d->registrylock.lockForRead(); - const KoColorSpace *cs = getCachedColorSpace(csID, profile->name()); - d->registrylock.unlock(); - if (!d->profileMap.contains(profile->name())) { - addProfile(profile); - } + const KoColorSpace *cs = 0; - if (!cs) { - // The profile was not stored and thus not the combination either - d->registrylock.lockForRead(); - KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(csID); - d->registrylock.unlock(); - if (!csf) { - dbgPigmentCSRegistry << "Unknown color space type :" << csf; - return 0; - } - if (!csf->profileIsCompatible(profile ) ) { - return 0; - } + { + QReadLocker l(®istrylock); + cs = getCachedColorSpaceImpl(csID, profile->name()); + } - QWriteLocker l(&d->registrylock); - // Check again, anything could've happened between the unlock and the write lock - cs = getCachedColorSpace(csID, profile->name()); - if (!cs) { - cs = csf->grabColorSpace(profile); - if (!cs) - return 0; - - QString name = idsToCacheName(csID, profile->name()); - d->csMap[name] = cs; - cs->d->deletability = OwnedByRegistryDoNotDelete; - dbgPigmentCSRegistry << "colorspace count: " << d->csMap.count() << ", adding name: " << name; - } + // the profile should have already been added to the registry by createColorProfile() method + KIS_SAFE_ASSERT_RECOVER(profileStorage.containsProfile(profile)) { + // warning! locking happens inside addProfile! + q->addProfile(profile); + } + + if (!cs) { + // The profile was not stored and thus not the combination either + QWriteLocker l(®istrylock); + KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID); + + if (!csf) { + dbgPigmentCSRegistry << "Unknown color space type :" << csf; + return 0; } - return cs; - } else { - return colorSpace(csID); + if (!csf->profileIsCompatible(profile)) { + dbgPigmentCSRegistry << "Profile is not compatible:" << csf << profile->name(); + return 0; + } + + cs = lazyCreateColorSpaceImpl(csID, profile); } + + return cs; } const KoColorSpace * KoColorSpaceRegistry::alpha8() { if (!d->alphaCs) { - d->alphaCs = colorSpace(KoAlphaColorSpace::colorSpaceId()); + d->alphaCs = d->colorSpace1(KoAlphaColorSpace::colorSpaceId()); } Q_ASSERT(d->alphaCs); return d->alphaCs; } const KoColorSpace * KoColorSpaceRegistry::alpha16() { if (!d->alphaU16Cs) { - d->alphaU16Cs = colorSpace(KoAlphaU16ColorSpace::colorSpaceId()); + d->alphaU16Cs = d->colorSpace1(KoAlphaU16ColorSpace::colorSpaceId()); } Q_ASSERT(d->alphaU16Cs); return d->alphaU16Cs; } #ifdef HAVE_OPENEXR const KoColorSpace * KoColorSpaceRegistry::alpha16f() { if (!d->alphaF16Cs) { - d->alphaF16Cs = colorSpace(KoAlphaF16ColorSpace::colorSpaceId()); + d->alphaF16Cs = d->colorSpace1(KoAlphaF16ColorSpace::colorSpaceId()); } Q_ASSERT(d->alphaF16Cs); return d->alphaF16Cs; } #endif const KoColorSpace * KoColorSpaceRegistry::alpha32f() { if (!d->alphaF32Cs) { - d->alphaF32Cs = colorSpace(KoAlphaF32ColorSpace::colorSpaceId()); + d->alphaF32Cs = d->colorSpace1(KoAlphaF32ColorSpace::colorSpaceId()); } Q_ASSERT(d->alphaF32Cs); return d->alphaF32Cs; } const KoColorSpace * KoColorSpaceRegistry::rgb8(const QString &profileName) { if (profileName.isEmpty()) { if (!d->rgbU8sRGB) { - d->rgbU8sRGB = colorSpace(KoRgbU8ColorSpace::colorSpaceId()); + d->rgbU8sRGB = d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId()); } Q_ASSERT(d->rgbU8sRGB); return d->rgbU8sRGB; } - return colorSpace(KoRgbU8ColorSpace::colorSpaceId(), profileName); + return d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId(), profileName); } const KoColorSpace * KoColorSpaceRegistry::rgb8(const KoColorProfile * profile) { if (profile == 0) { if (!d->rgbU8sRGB) { - d->rgbU8sRGB = colorSpace(KoRgbU8ColorSpace::colorSpaceId()); + d->rgbU8sRGB = d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId()); } Q_ASSERT(d->rgbU8sRGB); return d->rgbU8sRGB; } - return colorSpace(KoRgbU8ColorSpace::colorSpaceId(), profile); + return d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId(), profile); } const KoColorSpace * KoColorSpaceRegistry::rgb16(const QString &profileName) { - return colorSpace(KoRgbU16ColorSpace::colorSpaceId(), profileName); + return d->colorSpace1(KoRgbU16ColorSpace::colorSpaceId(), profileName); } const KoColorSpace * KoColorSpaceRegistry::rgb16(const KoColorProfile * profile) { - return colorSpace(KoRgbU16ColorSpace::colorSpaceId(), profile); + return d->colorSpace1(KoRgbU16ColorSpace::colorSpaceId(), profile); } const KoColorSpace * KoColorSpaceRegistry::lab16(const QString &profileName) { if (profileName.isEmpty()) { if (!d->lab16sLAB) { - d->lab16sLAB = colorSpace(KoLabColorSpace::colorSpaceId(), profileName); + d->lab16sLAB = d->colorSpace1(KoLabColorSpace::colorSpaceId()); } return d->lab16sLAB; } - return colorSpace(KoLabColorSpace::colorSpaceId(), profileName); + return d->colorSpace1(KoLabColorSpace::colorSpaceId(), profileName); } const KoColorSpace * KoColorSpaceRegistry::lab16(const KoColorProfile * profile) { if (profile == 0) { if (!d->lab16sLAB) { - d->lab16sLAB = colorSpace(KoLabColorSpace::colorSpaceId(), profile); + d->lab16sLAB = d->colorSpace1(KoLabColorSpace::colorSpaceId()); } Q_ASSERT(d->lab16sLAB); return d->lab16sLAB; } - return colorSpace(KoLabColorSpace::colorSpaceId(), profile); + return d->colorSpace1(KoLabColorSpace::colorSpaceId(), profile); } QList KoColorSpaceRegistry::colorModelsList(ColorSpaceListVisibility option) const { QReadLocker l(&d->registrylock); + QList ids; QList factories = d->colorSpaceFactoryRegistry.values(); Q_FOREACH (KoColorSpaceFactory* factory, factories) { if (!ids.contains(factory->colorModelId()) && (option == AllColorSpaces || factory->userVisible())) { ids << factory->colorModelId(); } } return ids; } QList KoColorSpaceRegistry::colorDepthList(const KoID& colorModelId, ColorSpaceListVisibility option) const { return colorDepthList(colorModelId.id(), option); } QList KoColorSpaceRegistry::colorDepthList(const QString & colorModelId, ColorSpaceListVisibility option) const { QReadLocker l(&d->registrylock); + QList ids; QList factories = d->colorSpaceFactoryRegistry.values(); Q_FOREACH (KoColorSpaceFactory* factory, factories) { if (!ids.contains(KoID(factory->colorDepthId())) && factory->colorModelId().id() == colorModelId && (option == AllColorSpaces || factory->userVisible())) { ids << factory->colorDepthId(); } } return ids; } -QString KoColorSpaceRegistry::colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const +QString KoColorSpaceRegistry::Private::colorSpaceIdImpl(const QString & colorModelId, const QString & colorDepthId) const { - QReadLocker l(&d->registrylock); - QList factories = d->colorSpaceFactoryRegistry.values(); + QList factories = colorSpaceFactoryRegistry.values(); Q_FOREACH (KoColorSpaceFactory* factory, factories) { if (factory->colorModelId().id() == colorModelId && factory->colorDepthId().id() == colorDepthId) { return factory->id(); } } return ""; } +QString KoColorSpaceRegistry::colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const +{ + QReadLocker l(&d->registrylock); + return d->colorSpaceIdImpl(colorModelId, colorDepthId); +} + QString KoColorSpaceRegistry::colorSpaceId(const KoID& colorModelId, const KoID& colorDepthId) const { return colorSpaceId(colorModelId.id(), colorDepthId.id()); } KoID KoColorSpaceRegistry::colorSpaceColorModelId(const QString & _colorSpaceId) const { QReadLocker l(&d->registrylock); + KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(_colorSpaceId); if (factory) { return factory->colorModelId(); } else { return KoID(); } } KoID KoColorSpaceRegistry::colorSpaceColorDepthId(const QString & _colorSpaceId) const { QReadLocker l(&d->registrylock); + KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(_colorSpaceId); if (factory) { return factory->colorDepthId(); } else { return KoID(); } } const KoColorConversionSystem* KoColorSpaceRegistry::colorConversionSystem() const { return d->colorConversionSystem; } KoColorConversionCache* KoColorSpaceRegistry::colorConversionCache() const { return d->colorConversionCache; } const KoColorSpace* KoColorSpaceRegistry::permanentColorspace(const KoColorSpace* _colorSpace) { if (_colorSpace->d->deletability != NotOwnedByRegistry) { return _colorSpace; } else if (*_colorSpace == *d->alphaCs) { return d->alphaCs; } else { - const KoColorSpace* cs = colorSpace(_colorSpace->id(), _colorSpace->profile()); + const KoColorSpace* cs = d->colorSpace1(_colorSpace->id(), _colorSpace->profile()); Q_ASSERT(cs); Q_ASSERT(*cs == *_colorSpace); return cs; } } QList KoColorSpaceRegistry::listKeys() const { QReadLocker l(&d->registrylock); QList answer; Q_FOREACH (const QString& key, d->colorSpaceFactoryRegistry.keys()) { answer.append(KoID(key, d->colorSpaceFactoryRegistry.get(key)->name())); } return answer; } +struct KoColorSpaceRegistry::Private::ProfileRegistrationInterface : public KoColorSpaceFactory::ProfileRegistrationInterface +{ + ProfileRegistrationInterface(KoColorSpaceRegistry::Private *_d) : d(_d) {} + + const KoColorProfile* profileByName(const QString &profileName) const override { + return d->profileStorage.profileByName(profileName); + } + + void registerNewProfile(KoColorProfile *profile) override { + d->profileStorage.addProfile(profile); + d->colorConversionSystem->insertColorProfile(profile); + } + + KoColorSpaceRegistry::Private *d; +}; + const KoColorProfile* KoColorSpaceRegistry::createColorProfile(const QString& colorModelId, const QString& colorDepthId, const QByteArray& rawData) { - QReadLocker l(&d->registrylock); - KoColorSpaceFactory* factory_ = d->colorSpaceFactoryRegistry.get(colorSpaceId(colorModelId, colorDepthId)); - return factory_->colorProfile(rawData); + QWriteLocker l(&d->registrylock); + KoColorSpaceFactory* factory_ = d->colorSpaceFactoryRegistry.get(d->colorSpaceIdImpl(colorModelId, colorDepthId)); + + Private::ProfileRegistrationInterface interface(d); + return factory_->colorProfile(rawData, &interface); } QList KoColorSpaceRegistry::allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection) { QList colorSpaces; + // TODO: thread-unsafe code: the factories might change right after the lock in released + // HINT: used in a unittest only! + d->registrylock.lockForRead(); QList factories = d->colorSpaceFactoryRegistry.values(); d->registrylock.unlock(); Q_FOREACH (KoColorSpaceFactory* factory, factories) { // Don't test with ycbcr for now, since we don't have a default profile for it. if (factory->colorModelId().id().startsWith("Y")) continue; if (visibility == AllColorSpaces || factory->userVisible()) { if (pSelection == OnlyDefaultProfile) { - const KoColorSpace *cs = colorSpace(factory->id()); + const KoColorSpace *cs = d->colorSpace1(factory->id()); if (cs) { colorSpaces.append(cs); } else { warnPigment << "Could not create colorspace for id" << factory->id() << "since there is no working default profile"; } } else { QList profiles = KoColorSpaceRegistry::instance()->profilesFor(factory->id()); Q_FOREACH (const KoColorProfile * profile, profiles) { - const KoColorSpace *cs = colorSpace(factory->id(), profile); + const KoColorSpace *cs = d->colorSpace1(factory->id(), profile); if (cs) { colorSpaces.append(cs); } else { warnPigment << "Could not create colorspace for id" << factory->id() << "and profile" << profile->name(); } } } } } return colorSpaces; } diff --git a/libs/pigment/KoColorSpaceRegistry.h b/libs/pigment/KoColorSpaceRegistry.h index 1504102427..89abd8c935 100644 --- a/libs/pigment/KoColorSpaceRegistry.h +++ b/libs/pigment/KoColorSpaceRegistry.h @@ -1,371 +1,361 @@ /* * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004,2010 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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACEREGISTRY_H #define KOCOLORSPACEREGISTRY_H #include #include #include #include "kritapigment_export.h" #include #include #include class KoColorProfile; class KoColorConversionSystem; class KoColorConversionCache; +class KoColorConversionTransformation; /** * The registry for colorspaces and profiles. * This class contains: * - a registry of colorspace instantiated with specific profiles. * - a registry of singleton colorspace factories. * - a registry of icc profiles + * + * Locking policy details: + * + * Basically, we have two levels of locks in the registry: + * 1) (outer level) is Private::registrylock, which controls the structures + * of the color space registry itself + * 2) (inner level) is KoColorProfileStorage::Private::lock controls + * the structures related to profiles. + * + * The locks can be taken individually, but if you are going to take both + * of them, you should always follow the order 1) registry; 2) profiles. + * Otherwise you'll get a deadlock. + * + * To avoid recursive deadlocks, all the dependent classes + * (KoColorConversionSystem and KoColorSpaceFactory) now do not use the direct + * links to the registry. Instead, they use special private interfaces that + * skip recursive locking and ensure we take a lock twice. */ class KRITAPIGMENT_EXPORT KoColorSpaceRegistry { public: KoColorSpaceRegistry(); enum ColorSpaceListVisibility { OnlyUserVisible = 1, ///< Only user visible color space AllColorSpaces = 4 ///< All color space even those not visible to the user }; enum ColorSpaceListProfilesSelection { OnlyDefaultProfile = 1, ///< Only add the default profile AllProfiles = 4 ///< Add all profiles }; /** * Return an instance of the KoColorSpaceRegistry * Creates an instance if that has never happened before and returns the singleton instance. */ static KoColorSpaceRegistry * instance(); virtual ~KoColorSpaceRegistry(); public: /** * add a color space to the registry * @param item the color space factory to add */ void add(KoColorSpaceFactory* item); /** * Remove a color space factory from the registry. Note that it is the * responsibility of the caller to ensure that the colorspaces are not * used anymore. */ void remove(KoColorSpaceFactory* item); /** * Add a profile to the profile map but do not add it to the * color conversion system yet. * @param profile the new profile to be registered. */ void addProfileToMap(KoColorProfile *p); /** * register the profile with the color space registry * @param profile the new profile to be registered so it can be combined with * colorspaces. */ void addProfile(KoColorProfile* profile); void addProfile(const KoColorProfile* profile); // TODO why ? void removeProfile(KoColorProfile* profile); /** * Create an alias to a profile with a different name. Then @ref profileByName * will return the profile @p to when passed @p name as a parameter. */ void addProfileAlias(const QString& name, const QString& to); /** * @return the profile alias, or name if not aliased */ QString profileAlias(const QString& name) const; /** * create a profile of the specified type. */ const KoColorProfile *createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData); /** * Return a profile by its given name, or 0 if none registered. * @return a profile by its given name, or 0 if none registered. * @param name the product name as set on the profile. * @see addProfile() * @see KoColorProfile::productName() */ const KoColorProfile * profileByName(const QString & name) const ; /** * Returns a profile by its unique id stored/calculated in the header. * The first call to this function might take long, because the map is * created on the first use only (atm used by SVG only) * @param id unique ProfileID of the profile (MD5 sum of its header) * @return the profile or 0 if not found */ const KoColorProfile *profileByUniqueId(const QByteArray &id) const; - /** - * Return the list of profiles for the argument colorspacefactory. - * Profiles will not work with any color space, you can query which profiles - * that are registered with this registry can be used in combination with the - * argument factory. - * @param factory the factory with which all the returned profiles will work. - * @return a list of profiles for the factory - */ - QList profilesFor(const KoColorSpaceFactory * factory) const; - - /** - * Return the list of profiles for a colorspace with the argument id. - * Profiles will not work with any color space, you can query which profiles - * that are registered with this registry can be used in combination with the - * argument factory. - * @param id the colorspace-id with which all the returned profiles will work. - * @return a list of profiles for the factory - */ - QList profilesFor(const KoID& id) const; - - /** - * @return a list of color spaces compatible with this profile - */ - QList colorSpacesFor(const KoColorProfile* _profile) const; + bool profileIsCompatible(const KoColorProfile* profile, const QString &colorSpaceId); /** * Return the list of profiles for a colorspace with the argument id. * Profiles will not work with any color space, you can query which profiles * that are registered with this registry can be used in combination with the * argument factory. * @param colorSpaceId the colorspace-id with which all the returned profiles will work. * @return a list of profiles for the factory */ - QList profilesFor(const QString& id) const; - const KoColorSpaceFactory* colorSpaceFactory(const QString &colorSpaceId) const; + QList profilesFor(const QString& csID) const; + QString defaultProfileForColorSpace(const QString &colorSpaceId) const; -private: /** - * Return a colorspace that works with the parameter profile. - * @param csID the ID of the colorspace that you want to have returned - * @param profileName the name of the KoColorProfile to be combined with the colorspace - * @return the wanted colorspace, or 0 when the cs and profile can not be combined. + * This function is called by the color space to create a color conversion + * between two color space. This function search in the graph of transformations + * the best possible path between the two color space. */ - const KoColorSpace * colorSpace(const KoID &csID, const QString & profileName); + KoColorConversionTransformation* createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** - * Return a colorspace that works with the parameter profile. - * @param colorSpaceId the ID string of the colorspace that you want to have returned - * @param profile the profile be combined with the colorspace - * @return the wanted colorspace, or 0 when the cs and profile can not be combined. + * This function creates two transformations, one from the color space and one to the + * color space. The destination color space is picked from a list of color space, such + * as the conversion between the two color space is of the best quality. + * + * The typical use case of this function is for KoColorTransformationFactory which + * doesn't support all color spaces, so unsupported color space have to find an + * acceptable conversion in order to use that KoColorTransformationFactory. + * + * @param colorSpace the source color space + * @param possibilities a list of color space among which we need to find the best + * conversion + * @param fromCS the conversion from the source color space will be affected to this + * variable + * @param toCS the revert conversion to the source color space will be affected to this + * variable */ - const KoColorSpace * colorSpace(const QString &colorSpaceId, const KoColorProfile *profile); + void createColorConverters(const KoColorSpace* colorSpace, const QList< QPair >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const; - /** - * Return a colorspace that works with the parameter profile. - * @param profileName the name of the KoColorProfile to be combined with the colorspace - * @return the wanted colorspace, or 0 when the cs and profile can not be combined. - */ - const KoColorSpace * colorSpace(const QString &colorSpaceId, const QString &profileName = QString()); -public: /** * Return a colorspace that works with the parameter profile. * @param colorSpaceId the ID string of the colorspace that you want to have returned * @param profile the profile be combined with the colorspace * @return the wanted colorspace, or 0 when the cs and profile can not be combined. */ const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const KoColorProfile *profile); /** * Return a colorspace that works with the parameter profile. * @param profileName the name of the KoColorProfile to be combined with the colorspace * @return the wanted colorspace, or 0 when the cs and profile can not be combined. */ const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName); + const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId); + /** * Return the id of the colorspace that have the defined colorModelId with colorDepthId. * @param colorModelId id of the color model * @param colorDepthId id of the color depth * @return the id of the wanted colorspace, or "" if no colorspace correspond to those ids */ QString colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const; /** * It's a convenient function that behave like the above. * Return the id of the colorspace that have the defined colorModelId with colorDepthId. * @param colorModelId id of the color model * @param colorDepthId id of the color depth * @return the id of the wanted colorspace, or "" if no colorspace correspond to those ids */ QString colorSpaceId(const KoID& colorModelId, const KoID& colorDepthId) const; /** * @return a the identifiant of the color model for the given color space id. * * This function is a compatibility function used to get the color space from * all kra files. */ KoID colorSpaceColorModelId(const QString & _colorSpaceId) const; /** * @return a the identifiant of the color depth for the given color space id. * * This function is a compatibility function used to get the color space from * all kra files. */ KoID colorSpaceColorDepthId(const QString & _colorSpaceId) const; /** * Convenience methods to get the often used alpha colorspaces */ const KoColorSpace *alpha8(); const KoColorSpace *alpha16(); #include #ifdef HAVE_OPENEXR const KoColorSpace *alpha16f(); #endif const KoColorSpace *alpha32f(); /** * Convenience method to get an RGBA 8bit colorspace. If a profile is not specified, * an sRGB profile will be used. * @param profileName the name of an RGB color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb8(const QString &profileName = QString()); /** * Convenience method to get an RGBA 8bit colorspace with the given profile. * @param profile an RGB profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb8(const KoColorProfile * profile); /** * Convenience method to get an RGBA 16bit colorspace. If a profile is not specified, * an sRGB profile will be used. * @param profileName the name of an RGB color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb16(const QString &profileName = QString()); /** * Convenience method to get an RGBA 16bit colorspace with the given profile. * @param profile an RGB profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb16(const KoColorProfile * profile); /** * Convenience method to get an Lab 16bit colorspace. If a profile is not specified, * an Lab profile with a D50 whitepoint will be used. * @param profileName the name of an Lab color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * lab16(const QString &profileName = QString()); /** * Convenience method to get an Lab 16bit colorspace with the given profile. * @param profile an Lab profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * lab16(const KoColorProfile * profile); /** * @return the list of available color models */ QList colorModelsList(ColorSpaceListVisibility option) const; /** * @return the list of available color models for the given colorModelId */ QList colorDepthList(const KoID& colorModelId, ColorSpaceListVisibility option) const; /** * @return the list of available color models for the given colorModelId */ QList colorDepthList(const QString & colorModelId, ColorSpaceListVisibility option) const; - /** - * @return the color conversion system use by the registry and the color - * spaces to create color conversion transformation - */ - const KoColorConversionSystem* colorConversionSystem() const; - /** * @return the cache of color conversion transformation to be use by KoColorSpace */ KoColorConversionCache* colorConversionCache() const; /** * @return a permanent colorspace owned by the registry, of the same type and profile * as the one given in argument */ const KoColorSpace* permanentColorspace(const KoColorSpace* _colorSpace); /** * This function return a list of all the keys in KoID format by using the name() method * on the objects stored in the registry. */ QList listKeys() const; private: friend class KisCsConversionTest; friend class KisIteratorTest; friend class KisPainterTest; friend class KisCrashFilterTest; friend class KoColorSpacesBenchmark; friend class TestKoColorSpaceSanity; friend class KisActionRecorderTest; + friend class TestColorConversionSystem; + friend class FriendOfColorSpaceRegistry; + /** * @return a list with an instance of all color space with their default profile. */ QList allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection); -private: - /** - * The function checks if a colorspace with a certain id and profile name can be found in the cache - * NOTE: the function doesn't take any lock but it needs to be called inside a d->registryLock - * locked either in read or write. - * @param csId The colorspace id - * @param profileName The colorspace profile name - * @retval KoColorSpace The matching colorspace - * @retval 0 Null pointer if not match + * @return the color conversion system use by the registry and the color + * spaces to create color conversion transformation. + * + * WARNING: conversion system is guared by the registry locks, don't + * use it anywhere other than unttests! */ - const KoColorSpace* getCachedColorSpace(const QString & csId, const QString & profileName) const; - - QString idsToCacheName(const QString & csId, const QString & profileName) const; + const KoColorConversionSystem* colorConversionSystem() const; private: KoColorSpaceRegistry(const KoColorSpaceRegistry&); KoColorSpaceRegistry operator=(const KoColorSpaceRegistry&); void init(); private: struct Private; Private * const d; }; #endif // KOCOLORSPACEREGISTRY_H diff --git a/libs/pigment/KoCompositeOpRegistry.cpp b/libs/pigment/KoCompositeOpRegistry.cpp index 72fcf9772c..d2ac141e2a 100644 --- a/libs/pigment/KoCompositeOpRegistry.cpp +++ b/libs/pigment/KoCompositeOpRegistry.cpp @@ -1,214 +1,215 @@ /* * Copyright (c) 2005 Adrian Page * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCompositeOpRegistry.h" #include #include #include #include #include "KoCompositeOp.h" #include "KoColorSpace.h" Q_GLOBAL_STATIC(KoCompositeOpRegistry, registry) KoCompositeOpRegistry::KoCompositeOpRegistry() { m_categories << KoID("arithmetic", i18n("Arithmetic")) << KoID("dark" , i18n("Darken")) << KoID("light" , i18n("Lighten")) << KoID("negative" , i18n("Negative")) << KoID("mix" , i18n("Mix")) << KoID("misc" , i18n("Misc")) << KoID("hsy" , i18n("HSY")) << KoID("hsi" , i18n("HSI")) << KoID("hsl" , i18n("HSL")) << KoID("hsv" , i18n("HSV")); m_map.insert(m_categories[0], KoID(COMPOSITE_ADD , i18n("Addition"))); m_map.insert(m_categories[0], KoID(COMPOSITE_SUBTRACT , i18n("Subtract"))); m_map.insert(m_categories[0], KoID(COMPOSITE_MULT , i18n("Multiply"))); m_map.insert(m_categories[0], KoID(COMPOSITE_DIVIDE , i18n("Divide"))); m_map.insert(m_categories[0], KoID(COMPOSITE_INVERSE_SUBTRACT, i18n("Inverse Subtract"))); m_map.insert(m_categories[1], KoID(COMPOSITE_BURN , i18n("Burn"))); m_map.insert(m_categories[1], KoID(COMPOSITE_LINEAR_BURN, i18n("Linear Burn"))); m_map.insert(m_categories[1], KoID(COMPOSITE_DARKEN , i18n("Darken"))); m_map.insert(m_categories[1], KoID(COMPOSITE_GAMMA_DARK , i18n("Gamma Dark"))); m_map.insert(m_categories[1], KoID(COMPOSITE_DARKER_COLOR , i18n("Darker Color"))); m_map.insert(m_categories[2], KoID(COMPOSITE_DODGE , i18n("Color Dodge"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LIGHTEN , i18n("Lighten"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SCREEN , i18n("Screen"))); m_map.insert(m_categories[2], KoID(COMPOSITE_PIN_LIGHT , i18n("Pin Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_VIVID_LIGHT , i18n("Vivid Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_HARD_LIGHT , i18n("Hard Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LIGHTER_COLOR , i18n("Lighter Color"))); m_map.insert(m_categories[3], KoID(COMPOSITE_DIFF , i18n("Difference"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EQUIVALENCE , i18n("Equivalence"))); m_map.insert(m_categories[3], KoID(COMPOSITE_ADDITIVE_SUBTRACTIVE, i18n("Additive Subtractive"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EXCLUSION , i18n("Exclusion"))); m_map.insert(m_categories[3], KoID(COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent"))); m_map.insert(m_categories[4], KoID(COMPOSITE_OVER , i18n("Normal"))); m_map.insert(m_categories[4], KoID(COMPOSITE_BEHIND , i18n("Behind"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GREATER , i18n("Greater"))); m_map.insert(m_categories[4], KoID(COMPOSITE_OVERLAY , i18n("Overlay"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ERASE , i18n("Erase"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ALPHA_DARKEN , i18n("Alpha Darken"))); m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_MIX , i18n("Hard Mix"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GRAIN_MERGE , i18n("Grain Merge"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract"))); m_map.insert(m_categories[4], KoID(COMPOSITE_PARALLEL , i18n("Parallel"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ALLANON , i18n("Allanon"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GEOMETRIC_MEAN , i18n("Geometric Mean"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DESTINATION_ATOP, i18n("Destination Atop"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DESTINATION_IN , i18n("Destination In"))); + m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay"))); m_map.insert(m_categories[5], KoID(COMPOSITE_BUMPMAP , i18n("Bumpmap"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Map"))); m_map.insert(m_categories[5], KoID(COMPOSITE_DISSOLVE , i18n("Dissolve"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_RED , i18n("Copy Red"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_GREEN, i18n("Copy Green"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_BLUE , i18n("Copy Blue"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY , i18n("Copy"))); m_map.insert(m_categories[5], KoID(COMPOSITE_TANGENT_NORMALMAP, i18n("Tangent Normalmap"))); m_map.insert(m_categories[6], KoID(COMPOSITE_COLOR , i18n("Color"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HUE , i18n("Hue"))); m_map.insert(m_categories[6], KoID(COMPOSITE_SATURATION , i18n("Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_LUMINIZE , i18n("Luminosity"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INC_SATURATION, i18n("Increase Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COLOR_HSI , i18n("Color HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_HUE_HSI , i18n("Hue HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_SATURATION_HSI , i18n("Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INTENSITY , i18n("Intensity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INC_INTENSITY , i18n("Increase Intensity"))); m_map.insert(m_categories[8], KoID(COMPOSITE_COLOR_HSL , i18n("Color HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_HUE_HSL , i18n("Hue HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_SATURATION_HSL , i18n("Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_LIGHTNESS , i18n("Lightness"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness"))); m_map.insert(m_categories[9], KoID(COMPOSITE_COLOR_HSV , i18n("Color HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_HUE_HSV , i18n("Hue HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_SATURATION_HSV , i18n("Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_VALUE , i18n("Value"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_VALUE , i18n("Decrease Value"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_VALUE , i18n("Increase Value"))); } const KoCompositeOpRegistry& KoCompositeOpRegistry::instance() { return *registry; } KoID KoCompositeOpRegistry::getDefaultCompositeOp() const { return KoID(COMPOSITE_OVER, i18n("Normal")); } KoID KoCompositeOpRegistry::getKoID(const QString& compositeOpID) const { KoIDMap::const_iterator itr = qFind(m_map.begin(), m_map.end(), KoID(compositeOpID)); return (itr != m_map.end()) ? *itr : KoID(); } KoCompositeOpRegistry::KoIDMap KoCompositeOpRegistry::getCompositeOps() const { return m_map; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCategories() const { return m_categories; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoID& category, const KoColorSpace* colorSpace) const { qint32 num = m_map.count(category); KoIDMap::const_iterator beg = m_map.find(category); KoIDMap::const_iterator end = beg + num; KoIDList list; list.reserve(num); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoColorSpace* colorSpace) const { KoIDMap::const_iterator beg = m_map.begin(); KoIDMap::const_iterator end = m_map.end(); KoIDList list; list.reserve(m_map.size()); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } bool KoCompositeOpRegistry::colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const { return colorSpace ? colorSpace->hasCompositeOp(compositeOp.id()) : false; } diff --git a/libs/pigment/KoCompositeOpRegistry.h b/libs/pigment/KoCompositeOpRegistry.h index 5461712840..ebc7151463 100644 --- a/libs/pigment/KoCompositeOpRegistry.h +++ b/libs/pigment/KoCompositeOpRegistry.h @@ -1,175 +1,176 @@ /* * Copyright (c) 2005 Adrian Page * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOMPOSITEOPREGISTRY_H #define KOCOMPOSITEOPREGISTRY_H #include #include #include #include #include "kritapigment_export.h" class KoColorSpace; #include // TODO : convert this data blob into a modern design with an enum class. // This will reduce the need for runtime string comparisons. const QString COMPOSITE_OVER = "normal"; const QString COMPOSITE_ERASE = "erase"; const QString COMPOSITE_IN = "in"; const QString COMPOSITE_OUT = "out"; const QString COMPOSITE_ALPHA_DARKEN = "alphadarken"; const QString COMPOSITE_DESTINATION_IN = "destination-in"; const QString COMPOSITE_DESTINATION_ATOP = "destination-atop"; const QString COMPOSITE_XOR = "xor"; const QString COMPOSITE_PLUS = "plus"; const QString COMPOSITE_MINUS = "minus"; const QString COMPOSITE_ADD = "add"; const QString COMPOSITE_SUBTRACT = "subtract"; const QString COMPOSITE_INVERSE_SUBTRACT = "inverse_subtract"; const QString COMPOSITE_DIFF = "diff"; const QString COMPOSITE_MULT = "multiply"; const QString COMPOSITE_DIVIDE = "divide"; const QString COMPOSITE_ARC_TANGENT = "arc_tangent"; const QString COMPOSITE_GEOMETRIC_MEAN = "geometric_mean"; const QString COMPOSITE_ADDITIVE_SUBTRACTIVE = "additive_subtractive"; const QString COMPOSITE_EQUIVALENCE = "equivalence"; const QString COMPOSITE_ALLANON = "allanon"; const QString COMPOSITE_PARALLEL = "parallel"; const QString COMPOSITE_GRAIN_MERGE = "grain_merge"; const QString COMPOSITE_GRAIN_EXTRACT = "grain_extract"; const QString COMPOSITE_EXCLUSION = "exclusion"; const QString COMPOSITE_HARD_MIX = "hard mix"; const QString COMPOSITE_OVERLAY = "overlay"; const QString COMPOSITE_BEHIND = "behind"; -const QString COMPOSITE_GREATER = "greater"; +const QString COMPOSITE_GREATER = "greater"; +const QString COMPOSITE_HARD_OVERLAY = "hard overlay"; const QString COMPOSITE_DARKEN = "darken"; const QString COMPOSITE_BURN = "burn";//this is also known as 'color burn'. const QString COMPOSITE_LINEAR_BURN = "linear_burn"; const QString COMPOSITE_GAMMA_DARK = "gamma_dark"; const QString COMPOSITE_LIGHTEN = "lighten"; const QString COMPOSITE_DODGE = "dodge"; const QString COMPOSITE_LINEAR_DODGE = "linear_dodge"; const QString COMPOSITE_SCREEN = "screen"; const QString COMPOSITE_HARD_LIGHT = "hard_light"; const QString COMPOSITE_SOFT_LIGHT_PHOTOSHOP = "soft_light"; const QString COMPOSITE_SOFT_LIGHT_SVG = "soft_light_svg"; const QString COMPOSITE_GAMMA_LIGHT = "gamma_light"; const QString COMPOSITE_VIVID_LIGHT = "vivid_light"; const QString COMPOSITE_LINEAR_LIGHT = "linear light"; const QString COMPOSITE_PIN_LIGHT = "pin_light"; const QString COMPOSITE_HUE = "hue"; const QString COMPOSITE_COLOR = "color"; const QString COMPOSITE_SATURATION = "saturation"; const QString COMPOSITE_INC_SATURATION = "inc_saturation"; const QString COMPOSITE_DEC_SATURATION = "dec_saturation"; const QString COMPOSITE_LUMINIZE = "luminize"; const QString COMPOSITE_INC_LUMINOSITY = "inc_luminosity"; const QString COMPOSITE_DEC_LUMINOSITY = "dec_luminosity"; const QString COMPOSITE_HUE_HSV = "hue_hsv"; const QString COMPOSITE_COLOR_HSV = "color_hsv"; const QString COMPOSITE_SATURATION_HSV = "saturation_hsv"; const QString COMPOSITE_INC_SATURATION_HSV = "inc_saturation_hsv"; const QString COMPOSITE_DEC_SATURATION_HSV = "dec_saturation_hsv"; const QString COMPOSITE_VALUE = "value"; const QString COMPOSITE_INC_VALUE = "inc_value"; const QString COMPOSITE_DEC_VALUE = "dec_value"; const QString COMPOSITE_HUE_HSL = "hue_hsl"; const QString COMPOSITE_COLOR_HSL = "color_hsl"; const QString COMPOSITE_SATURATION_HSL = "saturation_hsl"; const QString COMPOSITE_INC_SATURATION_HSL = "inc_saturation_hsl"; const QString COMPOSITE_DEC_SATURATION_HSL = "dec_saturation_hsl"; const QString COMPOSITE_LIGHTNESS = "lightness"; const QString COMPOSITE_INC_LIGHTNESS = "inc_lightness"; const QString COMPOSITE_DEC_LIGHTNESS = "dec_lightness"; const QString COMPOSITE_HUE_HSI = "hue_hsi"; const QString COMPOSITE_COLOR_HSI = "color_hsi"; const QString COMPOSITE_SATURATION_HSI = "saturation_hsi"; const QString COMPOSITE_INC_SATURATION_HSI = "inc_saturation_hsi"; const QString COMPOSITE_DEC_SATURATION_HSI = "dec_saturation_hsi"; const QString COMPOSITE_INTENSITY = "intensity"; const QString COMPOSITE_INC_INTENSITY = "inc_intensity"; const QString COMPOSITE_DEC_INTENSITY = "dec_intensity"; const QString COMPOSITE_COPY = "copy"; const QString COMPOSITE_COPY_RED = "copy_red"; const QString COMPOSITE_COPY_GREEN = "copy_green"; const QString COMPOSITE_COPY_BLUE = "copy_blue"; const QString COMPOSITE_TANGENT_NORMALMAP = "tangent_normalmap"; const QString COMPOSITE_COLORIZE = "colorize"; const QString COMPOSITE_BUMPMAP = "bumpmap"; const QString COMPOSITE_COMBINE_NORMAL = "combine_normal"; const QString COMPOSITE_CLEAR = "clear"; const QString COMPOSITE_DISSOLVE = "dissolve"; const QString COMPOSITE_DISPLACE = "displace"; const QString COMPOSITE_NO = "nocomposition"; const QString COMPOSITE_PASS_THROUGH = "pass through"; // XXX: not implemented anywhere yet const QString COMPOSITE_DARKER_COLOR = "darker color"; const QString COMPOSITE_LIGHTER_COLOR = "lighter color"; const QString COMPOSITE_UNDEF = "undefined"; class KRITAPIGMENT_EXPORT KoCompositeOpRegistry { typedef QMultiMap KoIDMap; typedef QList KoIDList; public: KoCompositeOpRegistry(); static const KoCompositeOpRegistry& instance(); KoID getDefaultCompositeOp() const; KoID getKoID(const QString& compositeOpID) const; KoIDMap getCompositeOps() const; KoIDList getCategories() const; KoIDList getCompositeOps(const KoColorSpace* colorSpace) const; KoIDList getCompositeOps(const KoID& category, const KoColorSpace* colorSpace=0) const; bool colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const; template KoIDList filterCompositeOps(TKoIdIterator begin, TKoIdIterator end, const KoColorSpace* colorSpace, bool removeInvaliOps=true) const { KoIDList list; for(; begin!=end; ++begin){ if( colorSpaceHasCompositeOp(colorSpace, *begin) == removeInvaliOps) list.push_back(*begin); } return list; } private: KoIDList m_categories; KoIDMap m_map; }; #endif // KOCOMPOSITEOPREGISTRY_H diff --git a/libs/pigment/colorspaces/KoAlphaColorSpace.h b/libs/pigment/colorspaces/KoAlphaColorSpace.h index 4eaf4696fd..0f62a85b47 100644 --- a/libs/pigment/colorspaces/KoAlphaColorSpace.h +++ b/libs/pigment/colorspaces/KoAlphaColorSpace.h @@ -1,243 +1,243 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOALPHACOLORSPACE_H #define KOALPHACOLORSPACE_H #include #include "DebugPigment.h" #include "kritapigment_export.h" #include "KoColorSpaceAbstract.h" #include "KoColorSpaceTraits.h" #include "KoColorModelStandardIds.h" #include "KoColorModelStandardIdsUtils.h" #include "KoSimpleColorSpaceFactory.h" #include #ifdef HAVE_OPENEXR #include #endif template class KoColorSpaceMaths; typedef KoColorSpaceTrait AlphaU8Traits; typedef KoColorSpaceTrait AlphaU16Traits; typedef KoColorSpaceTrait AlphaF32Traits; template KoID alphaIdFromChannelType(); template <> inline KoID alphaIdFromChannelType() { return KoID("ALPHA", i18n("Alpha (8-bit integer)")); } template <> inline KoID alphaIdFromChannelType() { return KoID("ALPHAU16", i18n("Alpha (16-bit integer)")); } template <> inline KoID alphaIdFromChannelType() { return KoID("ALPHAF32", i18n("Alpha (32-bit floating point)")); } #ifdef HAVE_OPENEXR typedef KoColorSpaceTrait AlphaF16Traits; template <> inline KoID alphaIdFromChannelType() { return KoID("ALPHAF16", i18n("Alpha (16-bit floating point)")); } #endif class QBitArray; /** * The alpha mask is a special color strategy that treats all pixels as * alpha value with a color common to the mask. The default color is white. */ template class KRITAPIGMENT_EXPORT KoAlphaColorSpaceImpl : public KoColorSpaceAbstract<_CSTrait> { typedef typename _CSTrait::channels_type channels_type; typedef KoColorSpaceMaths _Maths; typedef KoColorSpaceMaths _MathsToU8; typedef KoColorSpaceMaths _MathsFromU8; public: KoAlphaColorSpaceImpl(); ~KoAlphaColorSpaceImpl() override; static QString colorSpaceId() { return alphaIdFromChannelType().id(); } KoID colorModelId() const override { return AlphaColorModelID; } KoID colorDepthId() const override { return colorDepthIdForChannelType(); } virtual KoColorSpace* clone() const; bool willDegrade(ColorSpaceIndependence independence) const override { Q_UNUSED(independence); return false; } bool profileIsCompatible(const KoColorProfile* /*profile*/) const override { return false; } void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const override; void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const override; quint8 difference(const quint8 *src1, const quint8 *src2) const override; quint8 differenceA(const quint8 *src1, const quint8 *src2) const override; quint32 colorChannelCount() const override { return 0; } QString channelValueText(const quint8 *pixel, quint32 channelIndex) const override; QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const override; virtual void convolveColors(quint8** colors, qreal* kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const; virtual quint32 colorSpaceType() const { return 0; } bool hasHighDynamicRange() const override { return false; } const KoColorProfile* profile() const override { return m_profile; } QImage convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const override; void toLabA16(const quint8* src, quint8* dst, quint32 nPixels) const override; void fromLabA16(const quint8* src, quint8* dst, quint32 nPixels) const override; void toRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const override; void fromRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const override; KoColorTransformation* createBrightnessContrastAdjustment(const quint16* transferValues) const override { Q_UNUSED(transferValues); warnPigment << i18n("Undefined operation in the alpha color space"); return 0; } KoColorTransformation* createPerChannelAdjustment(const quint16* const*) const override { warnPigment << i18n("Undefined operation in the alpha color space"); return 0; } KoColorTransformation *createDarkenAdjustment(qint32 , bool , qreal) const override { warnPigment << i18n("Undefined operation in the alpha color space"); return 0; } virtual void invertColor(quint8*, qint32) const { warnPigment << i18n("Undefined operation in the alpha color space"); } void colorToXML(const quint8* , QDomDocument& , QDomElement&) const override { warnPigment << i18n("Undefined operation in the alpha color space"); } void colorFromXML(quint8* , const QDomElement&) const override { warnPigment << i18n("Undefined operation in the alpha color space"); } void toHSY(const QVector &, qreal *, qreal *, qreal *) const override { warnPigment << i18n("Undefined operation in the alpha color space"); } QVector fromHSY(qreal *, qreal *, qreal *) const override { warnPigment << i18n("Undefined operation in the alpha color space"); QVector channelValues (1); channelValues.fill(0.0); return channelValues; } void toYUV(const QVector &, qreal *, qreal *, qreal *) const override { warnPigment << i18n("Undefined operation in the alpha color space"); } QVector fromYUV(qreal *, qreal *, qreal *) const override { warnPigment << i18n("Undefined operation in the alpha color space"); QVector channelValues (1); channelValues.fill(0.0); return channelValues; } protected: bool preferCompositionInSourceColorSpace() const override; private: KoColorProfile* m_profile; QList m_compositeOps; }; typedef KoAlphaColorSpaceImpl KoAlphaColorSpace; typedef KoAlphaColorSpaceImpl KoAlphaU16ColorSpace; #ifdef HAVE_OPENEXR typedef KoAlphaColorSpaceImpl KoAlphaF16ColorSpace; #endif typedef KoAlphaColorSpaceImpl KoAlphaF32ColorSpace; template class KoAlphaColorSpaceFactoryImpl : public KoSimpleColorSpaceFactory { typedef typename _CSTrait::channels_type channels_type; public: KoAlphaColorSpaceFactoryImpl() : KoSimpleColorSpaceFactory(alphaIdFromChannelType().id(), alphaIdFromChannelType().name(), - true, + false, AlphaColorModelID, colorDepthIdForChannelType(), qMin(16, int(sizeof(channels_type) * 8)), // DIRTY HACK ALERT: see a comment below! 100000) { /** * Note about a hack with reference bit depth: right now all the color * conversions to/from Alpha colorspace are done via LabAU16 16-bit color space, * therefore, the conversions are lossy! Better conversions are yet to be implemented, * but for now we just define this color space as having 16-bit reference depth * (the problem arises with AlphaF32 only of course, which is hardly used anywhere). */ } KoColorSpace *createColorSpace(const KoColorProfile *) const override { return new KoAlphaColorSpaceImpl<_CSTrait>(); } QList colorConversionLinks() const override; }; typedef KoAlphaColorSpaceFactoryImpl KoAlphaColorSpaceFactory; typedef KoAlphaColorSpaceFactoryImpl KoAlphaU16ColorSpaceFactory; #ifdef HAVE_OPENEXR typedef KoAlphaColorSpaceFactoryImpl KoAlphaF16ColorSpaceFactory; #endif typedef KoAlphaColorSpaceFactoryImpl KoAlphaF32ColorSpaceFactory; #endif diff --git a/libs/pigment/compositeops/KoCompositeOpFunctions.h b/libs/pigment/compositeops/KoCompositeOpFunctions.h index 437c204329..502c63b788 100644 --- a/libs/pigment/compositeops/KoCompositeOpFunctions.h +++ b/libs/pigment/compositeops/KoCompositeOpFunctions.h @@ -1,431 +1,444 @@ /* * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOMPOSITEOP_FUNCTIONS_H_ #define KOCOMPOSITEOP_FUNCTIONS_H_ #include template inline void cfReorientedNormalMapCombine(TReal srcR, TReal srcG, TReal srcB, TReal& dstR, TReal& dstG, TReal& dstB) { // see http://blog.selfshadow.com/publications/blending-in-detail/ by Barre-Brisebois and Hill TReal tx = 2*srcR-1; TReal ty = 2*srcG-1; TReal tz = 2*srcB; TReal ux = -2*dstR+1; TReal uy = -2*dstG+1; TReal uz = 2*dstB-1; TReal k = (tx*ux+ty*uy+tz*uz)/tz; // dot(t,u)/t.z TReal rx = tx*k-ux; TReal ry = ty*k-uy; TReal rz = tz*k-uz; k = 1/sqrt(rx*rx+ry*ry+rz*rz); // normalize result rx *= k; ry *= k; rz *= k; dstR = rx*0.5+0.5; dstG = ry*0.5+0.5; dstB = rz*0.5+0.5; } template inline void cfColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); dr = sr; dg = sg; db = sb; setLightness(dr, dg, db, lum); } template inline void cfLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { setLightness(dr, dg, db, getLightness(sr, sg, sb)); } template inline void cfIncreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { addLightness(dr, dg, db, getLightness(sr, sg, sb)); } template inline void cfDecreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { addLightness(dr, dg, db, getLightness(sr, sg, sb) - TReal(1.0)); } template inline void cfSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal sat = getSaturation(sr, sg, sb); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfIncreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal sat = lerp(getSaturation(dr,dg,db), unitValue(), getSaturation(sr,sg,sb)); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfDecreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal sat = lerp(zeroValue(), getSaturation(dr,dg,db), getSaturation(sr,sg,sb)); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfHue(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal sat = getSaturation(dr, dg, db); TReal lum = getLightness(dr, dg, db); dr = sr; dg = sg; db = sb; setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, lum); } template inline void cfTangentNormalmap(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal half=halfValue(); dr = sr+(dr-half); dg = sg+(dg-half); db = sb+(db-unitValue()); } template inline void cfDarkerColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); TReal lum2 = getLightness(sr, sg, sb); if (lum inline void cfLighterColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); TReal lum2 = getLightness(sr, sg, sb); if (lum>lum2) { sr = dr; sg = dg; sb = db; } else { dr = sr; dg = sg; db = sb; } } template inline T cfColorBurn(T src, T dst) { using namespace Arithmetic; if(dst == unitValue()) return unitValue(); T invDst = inv(dst); if(src < invDst) return zeroValue(); return inv(clamp(div(invDst, src))); } template inline T cfLinearBurn(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(src) + dst - unitValue()); } template inline T cfColorDodge(T src, T dst) { using namespace Arithmetic; if(dst == zeroValue()) return zeroValue(); T invSrc = inv(src); if(invSrc < dst) return unitValue(); return Arithmetic::clamp(div(dst, invSrc)); } template inline T cfAddition(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return Arithmetic::clamp(composite_type(src) + dst); } template inline T cfSubtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - src); } template inline T cfInverseSubtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - inv(src)); } template inline T cfExclusion(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type x = mul(src, dst); return clamp(composite_type(dst) + src - (x + x)); } template inline T cfDivide(T src, T dst) { using namespace Arithmetic; //typedef typename KoColorSpaceMathsTraits::compositetype composite_type; if(src == zeroValue()) return (dst == zeroValue()) ? zeroValue() : unitValue(); return clamp(div(dst, src)); } template inline T cfHardLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type src2 = composite_type(src) + src; if(src > halfValue()) { // screen(src*2.0 - 1.0, dst) src2 -= unitValue(); return T((src2+dst) - (src2*dst / unitValue())); } // multiply(src*2.0, dst) return clamp(src2*dst / unitValue()); } template inline T cfSoftLightSvg(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(fsrc > 0.5f) { qreal D = (fdst > 0.25f) ? sqrt(fdst) : ((16.0f*fdst - 12.0)*fdst + 4.0f)*fdst; return scale(fdst + (2.0f*fsrc - 1.0f) * (D - fdst)); } return scale(fdst - (1.0f - 2.0f * fsrc) * fdst * (1.0f - fdst)); } template inline T cfSoftLight(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(fsrc > 0.5f) { return scale(fdst + (2.0f * fsrc - 1.0f) * (sqrt(fdst) - fdst)); } return scale(fdst - (1.0f - 2.0f*fsrc) * fdst * (1.0f - fdst)); } template inline T cfVividLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; if(src < halfValue()) { if(src == zeroValue()) return (dst == unitValue()) ? unitValue() : zeroValue(); // min(1,max(0,1-(1-dst) / (2*src))) composite_type src2 = composite_type(src) + src; composite_type dsti = inv(dst); return clamp(unitValue() - (dsti * unitValue() / src2)); } if(src == unitValue()) return (dst == zeroValue()) ? zeroValue() : unitValue(); // min(1,max(0, dst / (2*(1-src))) composite_type srci2 = inv(src); srci2 += srci2; return clamp(composite_type(dst) * unitValue() / srci2); } template inline T cfPinLight(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // TODO: verify that the formula is correct (the first max would be useless here) // max(0, max(2*src-1, min(dst, 2*src))) composite_type src2 = composite_type(src) + src; composite_type a = qMin(dst, src2); composite_type b = qMax(src2-Arithmetic::unitValue(), a); return T(b); } template inline T cfArcTangent(T src, T dst) { using namespace Arithmetic; if(dst == zeroValue()) return (src == zeroValue()) ? zeroValue() : unitValue(); return scale(2.0 * atan(scale(src) / scale(dst)) / Arithmetic::pi); } template inline T cfAllanon(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // (dst + src) / 2 [or (dst + src) * 0.5] return T((composite_type(src) + dst) * halfValue() / unitValue()); } template inline T cfLinearLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // min(1,max(0,(dst + 2*src)-1)) return clamp((composite_type(src) + src + dst) - unitValue()); } template inline T cfParallel(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // min(max(2 / (1/dst + 1/src), 0), 1) composite_type unit = unitValue(); composite_type s = (src != zeroValue()) ? div(unit, src) : unit; composite_type d = (dst != zeroValue()) ? div(unit, dst) : unit; return clamp((unit+unit) * unit / (d+s)); } template inline T cfEquivalence(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // 1 - abs(dst - src) composite_type x = composite_type(dst) - src; return (x < Arithmetic::zeroValue()) ? T(-x) : T(x); } template inline T cfGrainMerge(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) + src - halfValue()); } template inline T cfGrainExtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - src + halfValue()); } template inline T cfHardMix(T src, T dst) { return (dst > Arithmetic::halfValue()) ? cfColorDodge(src,dst) : cfColorBurn(src,dst); } template inline T cfAdditiveSubtractive(T src, T dst) { using namespace Arithmetic; // min(1,max(0,abs(sqr(CB)-sqr(CT)))) qreal x = sqrt(scale(dst)) - sqrt(scale(src)); return scale((x < 0.0) ? -x : x); } template inline T cfGammaDark(T src, T dst) { using namespace Arithmetic; if(src == zeroValue()) return zeroValue(); // power(dst, 1/src) return scale(pow(scale(dst), 1.0/scale(src))); } template inline T cfGammaLight(T src, T dst) { using namespace Arithmetic; return scale(pow(scale(dst), scale(src))); } template inline T cfGeometricMean(T src, T dst) { using namespace Arithmetic; return scale(sqrt(scale(dst) * scale(src))); } template inline T cfOver(T src, T dst) { Q_UNUSED(dst); return src; } template inline T cfOverlay(T src, T dst) { return cfHardLight(dst, src); } template inline T cfMultiply(T src, T dst) { return Arithmetic::mul(src, dst); } +template +inline T cfHardOverlay(T src, T dst) { + using namespace Arithmetic; + + qreal fsrc = scale(src); + qreal fdst = scale(dst); + + if(fsrc > 0.5f) { + return scale(cfDivide(inv(2.0 * fsrc - 1.0f), fdst)); + } + return scale(mul(2.0 * fsrc, fdst)); +} + template inline T cfDifference(T src, T dst) { return qMax(src,dst) - qMin(src,dst); } template inline T cfScreen(T src, T dst) { return Arithmetic::unionShapeOpacity(src, dst); } template inline T cfDarkenOnly(T src, T dst) { return qMin(src, dst); } template inline T cfLightenOnly(T src, T dst) { return qMax(src, dst); } #endif // KOCOMPOSITEOP_FUNCTIONS_H_ diff --git a/libs/pigment/compositeops/KoCompositeOps.h b/libs/pigment/compositeops/KoCompositeOps.h index fb0f4ea771..fa4df10705 100644 --- a/libs/pigment/compositeops/KoCompositeOps.h +++ b/libs/pigment/compositeops/KoCompositeOps.h @@ -1,247 +1,248 @@ /* * Copyright (c) 2007 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KOCOMPOSITEOPS_H_ #define _KOCOMPOSITEOPS_H_ #include #include #include #include #include "compositeops/KoCompositeOpGeneric.h" #include "compositeops/KoCompositeOpOver.h" #include "compositeops/KoCompositeOpCopyChannel.h" #include "compositeops/KoCompositeOpAlphaDarken.h" #include "compositeops/KoCompositeOpErase.h" #include "compositeops/KoCompositeOpCopy2.h" #include "compositeops/KoCompositeOpDissolve.h" #include "compositeops/KoCompositeOpBehind.h" #include "compositeops/KoCompositeOpDestinationIn.h" #include "compositeops/KoCompositeOpDestinationAtop.h" #include "compositeops/KoCompositeOpGreater.h" #include "KoOptimizedCompositeOpFactory.h" namespace _Private { template struct AddGeneralOps { static void add(KoColorSpace* cs) { Q_UNUSED(cs); } }; template struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return new KoCompositeOpAlphaDarken(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return new KoCompositeOpOver(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp32(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp32(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return new KoCompositeOpAlphaDarken(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp128(cs); } }; template struct AddGeneralOps { typedef typename Traits::channels_type Arg; typedef Arg (*CompositeFunc)(Arg, Arg); static const qint32 alpha_pos = Traits::alpha_pos; template static void add(KoColorSpace* cs, const QString& id, const QString& description, const QString& category) { cs->addCompositeOp(new KoCompositeOpGenericSC(cs, id, description, category)); } static void add(KoColorSpace* cs) { cs->addCompositeOp(OptimizedOpsSelector::createOverOp(cs)); cs->addCompositeOp(OptimizedOpsSelector::createAlphaDarkenOp(cs)); cs->addCompositeOp(new KoCompositeOpCopy2(cs)); cs->addCompositeOp(new KoCompositeOpErase(cs)); cs->addCompositeOp(new KoCompositeOpBehind(cs)); cs->addCompositeOp(new KoCompositeOpDestinationIn(cs)); cs->addCompositeOp(new KoCompositeOpDestinationAtop(cs)); cs->addCompositeOp(new KoCompositeOpGreater(cs)); add<&cfOverlay >(cs, COMPOSITE_OVERLAY , i18n("Overlay") , KoCompositeOp::categoryMix()); add<&cfGrainMerge >(cs, COMPOSITE_GRAIN_MERGE , i18n("Grain Merge") , KoCompositeOp::categoryMix()); add<&cfGrainExtract >(cs, COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract") , KoCompositeOp::categoryMix()); add<&cfHardMix >(cs, COMPOSITE_HARD_MIX , i18n("Hard Mix") , KoCompositeOp::categoryMix()); add<&cfGeometricMean >(cs, COMPOSITE_GEOMETRIC_MEAN, i18n("Geometric Mean"), KoCompositeOp::categoryMix()); add<&cfParallel >(cs, COMPOSITE_PARALLEL , i18n("Parallel") , KoCompositeOp::categoryMix()); add<&cfAllanon >(cs, COMPOSITE_ALLANON , i18n("Allanon") , KoCompositeOp::categoryMix()); + add<&cfHardOverlay >(cs, COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay") , KoCompositeOp::categoryMix()); add<&cfScreen >(cs, COMPOSITE_SCREEN , i18n("Screen") , KoCompositeOp::categoryLight()); add<&cfColorDodge >(cs, COMPOSITE_DODGE , i18n("Color Dodge") , KoCompositeOp::categoryLight()); add<&cfAddition >(cs, COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"), KoCompositeOp::categoryLight()); add<&cfLightenOnly >(cs, COMPOSITE_LIGHTEN , i18n("Lighten") , KoCompositeOp::categoryLight()); add<&cfHardLight >(cs, COMPOSITE_HARD_LIGHT , i18n("Hard Light") , KoCompositeOp::categoryLight()); add<&cfSoftLightSvg >(cs, COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)") , KoCompositeOp::categoryLight()); add<&cfSoftLight >(cs, COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)") , KoCompositeOp::categoryLight()); add<&cfGammaLight >(cs, COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light") , KoCompositeOp::categoryLight()); add<&cfVividLight >(cs, COMPOSITE_VIVID_LIGHT , i18n("Vivid Light") , KoCompositeOp::categoryLight()); add<&cfPinLight >(cs, COMPOSITE_PIN_LIGHT , i18n("Pin Light") , KoCompositeOp::categoryLight()); add<&cfLinearLight >(cs, COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"), KoCompositeOp::categoryLight()); add<&cfColorBurn >(cs, COMPOSITE_BURN , i18n("Color Burn") , KoCompositeOp::categoryDark()); add<&cfLinearBurn >(cs, COMPOSITE_LINEAR_BURN , i18n("Linear Burn"), KoCompositeOp::categoryDark()); add<&cfDarkenOnly >(cs, COMPOSITE_DARKEN , i18n("Darken") , KoCompositeOp::categoryDark()); add<&cfGammaDark >(cs, COMPOSITE_GAMMA_DARK , i18n("Gamma Dark") , KoCompositeOp::categoryDark()); add<&cfAddition >(cs, COMPOSITE_ADD , i18n("Addition") , KoCompositeOp::categoryArithmetic()); add<&cfSubtract >(cs, COMPOSITE_SUBTRACT , i18n("Subtract") , KoCompositeOp::categoryArithmetic()); add<&cfInverseSubtract >(cs, COMPOSITE_INVERSE_SUBTRACT, i18n("Inversed-Subtract"), KoCompositeOp::categoryArithmetic()); add<&cfMultiply >(cs, COMPOSITE_MULT , i18n("Multiply") , KoCompositeOp::categoryArithmetic()); add<&cfDivide >(cs, COMPOSITE_DIVIDE , i18n("Divide") , KoCompositeOp::categoryArithmetic()); add<&cfArcTangent >(cs, COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent") , KoCompositeOp::categoryNegative()); add<&cfDifference >(cs, COMPOSITE_DIFF , i18n("Difference") , KoCompositeOp::categoryNegative()); add<&cfExclusion >(cs, COMPOSITE_EXCLUSION , i18n("Exclusion") , KoCompositeOp::categoryNegative()); add<&cfEquivalence >(cs, COMPOSITE_EQUIVALENCE , i18n("Equivalence") , KoCompositeOp::categoryNegative()); add<&cfAdditiveSubtractive >(cs, COMPOSITE_ADDITIVE_SUBTRACTIVE , i18n("Additive-Subtractive") , KoCompositeOp::categoryNegative()); cs->addCompositeOp(new KoCompositeOpDissolve(cs, KoCompositeOp::categoryMisc())); } }; template struct AddRGBOps { static void add(KoColorSpace* cs) { Q_UNUSED(cs); } }; template struct AddRGBOps { typedef float Arg; static const qint32 red_pos = Traits::red_pos; static const qint32 green_pos = Traits::green_pos; static const qint32 blue_pos = Traits::blue_pos; template static void add(KoColorSpace* cs, const QString& id, const QString& description, const QString& category) { cs->addCompositeOp(new KoCompositeOpGenericHSL(cs, id, description, category)); } static void add(KoColorSpace* cs) { cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_RED , i18n("Copy Red") , KoCompositeOp::categoryMisc())); cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_GREEN, i18n("Copy Green"), KoCompositeOp::categoryMisc())); cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_BLUE , i18n("Copy Blue") , KoCompositeOp::categoryMisc())); add<&cfTangentNormalmap >(cs, COMPOSITE_TANGENT_NORMALMAP , i18n("Tangent Normalmap") , KoCompositeOp::categoryMisc()); add<&cfReorientedNormalMapCombine >(cs, COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Maps"), KoCompositeOp::categoryMisc()); add<&cfColor >(cs, COMPOSITE_COLOR , i18n("Color") , KoCompositeOp::categoryHSY()); add<&cfHue >(cs, COMPOSITE_HUE , i18n("Hue") , KoCompositeOp::categoryHSY()); add<&cfSaturation >(cs, COMPOSITE_SATURATION , i18n("Saturation") , KoCompositeOp::categoryHSY()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION, i18n("Increase Saturation"), KoCompositeOp::categoryHSY()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"), KoCompositeOp::categoryHSY()); add<&cfLightness >(cs, COMPOSITE_LUMINIZE , i18n("Luminosity") , KoCompositeOp::categoryHSY()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"), KoCompositeOp::categoryHSY()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"), KoCompositeOp::categoryHSY()); add<&cfDarkerColor >(cs, COMPOSITE_DARKER_COLOR, i18n("Darker Color"), KoCompositeOp::categoryDark());//darker color as PSD does it// add<&cfLighterColor >(cs, COMPOSITE_LIGHTER_COLOR, i18n("Lighter Color"), KoCompositeOp::categoryLight());//lighter color as PSD does it// add<&cfColor >(cs, COMPOSITE_COLOR_HSI , i18n("Color HSI") , KoCompositeOp::categoryHSI()); add<&cfHue >(cs, COMPOSITE_HUE_HSI , i18n("Hue HSI") , KoCompositeOp::categoryHSI()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSI , i18n("Saturation HSI") , KoCompositeOp::categoryHSI()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"), KoCompositeOp::categoryHSI()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"), KoCompositeOp::categoryHSI()); add<&cfLightness >(cs, COMPOSITE_INTENSITY , i18n("Intensity") , KoCompositeOp::categoryHSI()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_INTENSITY , i18n("Increase Intensity") , KoCompositeOp::categoryHSI()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity") , KoCompositeOp::categoryHSI()); add<&cfColor >(cs, COMPOSITE_COLOR_HSL , i18n("Color HSL") , KoCompositeOp::categoryHSL()); add<&cfHue >(cs, COMPOSITE_HUE_HSL , i18n("Hue HSL") , KoCompositeOp::categoryHSL()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSL , i18n("Saturation HSL") , KoCompositeOp::categoryHSL()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"), KoCompositeOp::categoryHSL()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"), KoCompositeOp::categoryHSL()); add<&cfLightness >(cs, COMPOSITE_LIGHTNESS , i18n("Lightness") , KoCompositeOp::categoryHSL()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness") , KoCompositeOp::categoryHSL()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness") , KoCompositeOp::categoryHSL()); add<&cfColor >(cs, COMPOSITE_COLOR_HSV , i18n("Color HSV") , KoCompositeOp::categoryHSV()); add<&cfHue >(cs, COMPOSITE_HUE_HSV , i18n("Hue HSV") , KoCompositeOp::categoryHSV()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSV , i18n("Saturation HSV") , KoCompositeOp::categoryHSV()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"), KoCompositeOp::categoryHSV()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"), KoCompositeOp::categoryHSV()); add<&cfLightness >(cs, COMPOSITE_VALUE , i18n("Value") , KoCompositeOp::categoryHSV()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_VALUE , i18n("Increase Value") , KoCompositeOp::categoryHSV()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_VALUE , i18n("Decrease Value") , KoCompositeOp::categoryHSV()); } }; } /** * This function add to the colorspace all the composite ops defined by * the pigment library. */ template void addStandardCompositeOps(KoColorSpace* cs) { typedef typename _Traits_::channels_type channels_type; static const bool useGeneralOps = true; static const bool useRGBOps = (boost::is_base_of, _Traits_>::value || boost::is_base_of, _Traits_>::value); _Private::AddGeneralOps<_Traits_, useGeneralOps>::add(cs); _Private::AddRGBOps <_Traits_, useRGBOps >::add(cs); } #endif diff --git a/libs/pigment/tests/CCSGraph.cpp b/libs/pigment/tests/CCSGraph.cpp index 81019a111e..a1f57ca2f2 100644 --- a/libs/pigment/tests/CCSGraph.cpp +++ b/libs/pigment/tests/CCSGraph.cpp @@ -1,109 +1,119 @@ /* * Copyright (c) 2007-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; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorConversionSystem.h" #include #include #include +struct FriendOfColorSpaceRegistry { +static QString toDot() { + return KoColorSpaceRegistry::instance()->colorConversionSystem()->toDot(); +} + +static QString bestPathToDot(const QString &srcKey, const QString &dstKey) { + return KoColorSpaceRegistry::instance()->colorConversionSystem()->bestPathToDot(srcKey, dstKey); +} +}; + int main(int argc, char** argv) { QCoreApplication app(argc, argv); QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); // Initialize the list of options parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graphs"), i18n("return the list of available graphs"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graph"), i18n("specify the type of graph (see --graphs to get the full list, the default is full)"), QLatin1String("type"), QLatin1String("full"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the source color space"), QLatin1String("key"), QString())); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the destination color space"), QLatin1String("key"), QString())); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("output"), i18n("specify the output (can be ps or dot, the default is ps)"), QLatin1String("type"), QLatin1String("ps"))); parser.addPositionalArgument(QLatin1String("outputfile"), i18n("name of the output file")); parser.process(app); // PORTING SCRIPT: move this to after any parser.addOption if (parser.isSet("graphs")) { // Don't change those lines to use dbgPigment derivatives, they need to be outputed // to stdout not stderr. std::cout << "full : show all the connection on the graph" << std::endl; std::cout << "bestpath : show the best path for a given transformation" << std::endl; exit(EXIT_SUCCESS); } QString graphType = parser.value("graph"); QString outputType = parser.value("output"); if (parser.positionalArguments().count() != 1) { errorPigment << "No output file name specified"; parser.showHelp(); exit(EXIT_FAILURE); } QString outputFileName = parser.positionalArguments()[0]; // Generate the graph QString dot; if (graphType == "full") { - dot = KoColorSpaceRegistry::instance()->colorConversionSystem()->toDot(); + dot = FriendOfColorSpaceRegistry::toDot(); } else if (graphType == "bestpath") { QString srcKey = parser.value("src-key"); QString dstKey = parser.value("dst-key"); if (srcKey.isEmpty() || dstKey.isEmpty()) { errorPigment << "src-key and dst-key must be specified for the graph bestpath"; exit(EXIT_FAILURE); } else { - dot = KoColorSpaceRegistry::instance()->colorConversionSystem()->bestPathToDot(srcKey, dstKey); + dot = FriendOfColorSpaceRegistry::bestPathToDot(srcKey, dstKey); } } else { errorPigment << "Unknow graph type : " << graphType.toLatin1(); exit(EXIT_FAILURE); } if (outputType == "dot") { QFile file(outputFileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) exit(EXIT_FAILURE); QTextStream out(&file); out << dot; } else if (outputType == "ps" || outputType == "svg") { QTemporaryFile file; if (!file.open()) { exit(EXIT_FAILURE); } QTextStream out(&file); out << dot; QString cmd = QString("dot -T%1 %2 -o %3").arg(outputType).arg(file.fileName()).arg(outputFileName); file.close(); if (QProcess::execute(cmd) != 0) { errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from http://www.graphiz.org)"; } } else { errorPigment << "Unknow output type : " << outputType; exit(EXIT_FAILURE); } } diff --git a/libs/store/KoXmlReader.cpp b/libs/store/KoXmlReader.cpp index 5e1d00c2fe..7890266284 100644 --- a/libs/store/KoXmlReader.cpp +++ b/libs/store/KoXmlReader.cpp @@ -1,2351 +1,106 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat 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 "KoXmlReader.h" #include "KoXmlNS.h" -/* - This is a memory-efficient DOM implementation for Calligra. See the API - documentation for details. - - IMPORTANT ! - - * When you change this stuff, make sure it DOES NOT BREAK the test suite. - Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights - have been sacrificed for this piece of code, do not let those precious - hours wasted! - - * Run koxmlreadertest.cpp WITH Valgrind and make sure NO illegal - memory read/write and any type of leak occurs. If you are not familiar - with Valgrind then RTFM first and come back again later on. - - * The public API shall remain as compatible as QDom. - - * All QDom-compatible methods should behave the same. All QDom-compatible - functions should return the same result. In case of doubt, run - koxmlreadertest.cpp but uncomment KOXML_USE_QDOM in koxmlreader.h - so that the tests are performed with standard QDom. - - Some differences compared to QDom: - - - DOM tree in KoXmlDocument is read-only, you can not modify it. This is - sufficient for Calligra since the tree is only accessed when loading - a document to the application. For saving the document to XML file, - use KoXmlWriter. - - - Because the dynamic loading and unloading, you have to use the - nodes (and therefore also elements) carefully since the whole API - (just like QDom) is reference-based, not pointer-based. If the - parent node is unloaded from memory, the reference is not valid - anymore and may give unpredictable result. - The easiest way: use the node/element in very short time only. - - - Comment node (like QDomComment) is not implemented as comments are - simply ignored. - - - DTD, entity and entity reference are not handled. Thus, the associated - nodes (like QDomDocumentType, QDomEntity, QDomEntityReference) are also - not implemented. - - - Attribute mapping node is not implemented. But of course, functions to - query attributes of an element are available. - - - */ - -#include -#include - -#ifndef KOXML_USE_QDOM - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -/* - Use more compact representation of in-memory nodes. - - Advantages: faster iteration, can facilitate real-time compression. - Disadvantages: still buggy, eat slightly more memory. -*/ -#define KOXML_COMPACT - -/* - Use real-time compression. Only works in conjuction with KOXML_COMPACT - above because otherwise the non-compact layout will slow down everything. -*/ -#define KOXML_COMPRESS - - -// prevent mistake, see above -#ifdef KOXML_COMPRESS -#ifndef KOXML_COMPACT -#error Please enable also KOXML_COMPACT -#endif -#endif - -// this is used to quickly get namespaced attribute(s) -typedef QPair KoXmlStringPair; - -class KoQName { -public: - QString nsURI; - QString name; - - explicit KoQName(const QString& nsURI_, const QString& name_) - : nsURI(nsURI_), name(name_) {} - bool operator==(const KoQName& qname) const { - // local name is more likely to differ, so compare that first - return name == qname.name && nsURI == qname.nsURI; - } -}; - -uint qHash(const KoQName& qname) -{ - // possibly add a faster hash function that only includes some trailing - // part of the nsURI - - // in case of doubt, use this: - // return qHash(qname.nsURI)^qHash(qname.name); - return qHash(qname.nsURI)^qHash(qname.name); -} - -static inline bool operator==(const KoXmlStringPair &a, const KoXmlStringPair &b) -{ - return a.second == b.second && a.first == b.first; -} - -// Older versions of OpenOffice.org used different namespaces. This function -// does translate the old namespaces into the new ones. -static QString fixNamespace(const QString &nsURI) -{ - static QString office = QString::fromLatin1("http://openoffice.org/2000/office"); - static QString text = QString::fromLatin1("http://openoffice.org/2000/text"); - static QString style = QString::fromLatin1("http://openoffice.org/2000/style"); - static QString fo = QString::fromLatin1("http://www.w3.org/1999/XSL/Format"); - static QString table = QString::fromLatin1("http://openoffice.org/2000/table"); - static QString drawing = QString::fromLatin1("http://openoffice.org/2000/drawing"); - static QString datastyle = QString::fromLatin1("http://openoffice.org/2000/datastyle"); - static QString svg = QString::fromLatin1("http://www.w3.org/2000/svg"); - static QString chart = QString::fromLatin1("http://openoffice.org/2000/chart"); - static QString dr3d = QString::fromLatin1("http://openoffice.org/2000/dr3d"); - static QString form = QString::fromLatin1("http://openoffice.org/2000/form"); - static QString script = QString::fromLatin1("http://openoffice.org/2000/script"); - static QString meta = QString::fromLatin1("http://openoffice.org/2000/meta"); - static QString config = QString::fromLatin1("http://openoffice.org/2001/config"); - static QString pres = QString::fromLatin1("http://openoffice.org/2000/presentation"); - static QString manifest = QString::fromLatin1("http://openoffice.org/2001/manifest"); - if (nsURI == text) - return KoXmlNS::text; - if (nsURI == style) - return KoXmlNS::style; - if (nsURI == office) - return KoXmlNS::office; - if (nsURI == fo) - return KoXmlNS::fo; - if (nsURI == table) - return KoXmlNS::table; - if (nsURI == drawing) - return KoXmlNS::draw; - if (nsURI == datastyle) - return KoXmlNS::number; - if (nsURI == svg) - return KoXmlNS::svg; - if (nsURI == chart) - return KoXmlNS::chart; - if (nsURI == dr3d) - return KoXmlNS::dr3d; - if (nsURI == form) - return KoXmlNS::form; - if (nsURI == script) - return KoXmlNS::script; - if (nsURI == meta) - return KoXmlNS::meta; - if (nsURI == config) - return KoXmlNS::config; - if (nsURI == pres) - return KoXmlNS::presentation; - if (nsURI == manifest) - return KoXmlNS::manifest; - return nsURI; -} - -// ================================================================== -// -// KoXmlPackedItem -// -// ================================================================== - -// 12 bytes on most system 32 bit systems, 16 bytes on 64 bit systems -class KoXmlPackedItem -{ -public: -bool attr: 1; -KoXmlNode::NodeType type: 3; - -#ifdef KOXML_COMPACT -quint32 childStart: 28; -#else -unsigned depth: 28; -#endif - - unsigned qnameIndex; - QString value; - - // it is important NOT to have a copy constructor, so that growth is optimal - // see http://doc.trolltech.com/4.2/containers.html#growth-strategies -#if 0 - KoXmlPackedItem(): attr(false), type(KoXmlNode::NullNode), childStart(0), depth(0) {} -#endif -}; - -Q_DECLARE_TYPEINFO(KoXmlPackedItem, Q_MOVABLE_TYPE); - -#ifdef KOXML_COMPRESS -static QDataStream& operator<<(QDataStream& s, const KoXmlPackedItem& item) -{ - quint8 flag = item.attr ? 1 : 0; - - s << flag; - s << (quint8) item.type; - s << item.childStart; - s << item.qnameIndex; - s << item.value; - - return s; -} - -static QDataStream& operator>>(QDataStream& s, KoXmlPackedItem& item) -{ - quint8 flag; - quint8 type; - quint32 child; - QString value; - - s >> flag; - s >> type; - s >> child; - s >> item.qnameIndex; - s >> value; - - item.attr = (flag != 0); - item.type = (KoXmlNode::NodeType) type; - item.childStart = child; - item.value = value; - - return s; -} -#endif - -// ================================================================== -// -// KoXmlPackedDocument -// -// ================================================================== - -#ifdef KOXML_COMPRESS - -#include "KoXmlVector.h" - -// when number of buffered items reach this, compression will start -// small value will give better memory usage at the cost of speed -// bigger value will be better in term of speed, but use more memory -#define ITEMS_FULL (1*256) - -typedef KoXmlVector KoXmlPackedGroup; -#else -typedef QVector KoXmlPackedGroup; -#endif - -// growth strategy: increase every GROUP_GROW_SIZE items -// this will override standard QVector's growth strategy -#define GROUP_GROW_SHIFT 3 -#define GROUP_GROW_SIZE (1 << GROUP_GROW_SHIFT) - -class KoXmlPackedDocument -{ -public: - bool processNamespace; -#ifdef KOXML_COMPACT - // map given depth to the list of items - QHash groups; -#else - QVector items; -#endif - - QList qnameList; - QString docType; - -private: - QHash qnameHash; - - unsigned cacheQName(const QString& name, const QString& nsURI) { - KoQName qname(nsURI, name); - - const unsigned ii = qnameHash.value(qname, (unsigned)-1); - if (ii != (unsigned)-1) - return ii; - - // not yet declared, so we add it - unsigned i = qnameList.count(); - qnameList.append(qname); - qnameHash.insert(qname, i); - - return i; - } - - QHash valueHash; - QStringList valueList; - - QString cacheValue(const QString& value) { - if (value.isEmpty()) - return 0; - - const unsigned& ii = valueHash[value]; - if (ii > 0) - return valueList[ii]; - - // not yet declared, so we add it - unsigned i = valueList.count(); - valueList.append(value); - valueHash.insert(value, i); - - return valueList[i]; - } - -#ifdef KOXML_COMPACT -public: - const KoXmlPackedItem& itemAt(unsigned depth, unsigned index) { - const KoXmlPackedGroup& group = groups[depth]; - return group[index]; - } - - unsigned itemCount(unsigned depth) { - const KoXmlPackedGroup& group = groups[depth]; - return group.count(); - } - - /* - NOTE: - Function clear, newItem, addElement, addAttribute, addText, - addCData, addProcessing are all related. These are all necessary - for stateful manipulation of the document. See also the calls - to these function from parseDocument(). - - The state itself is defined by the member variables - currentDepth and the groups (see above). - */ - - unsigned currentDepth; - - KoXmlPackedItem& newItem(unsigned depth) { - KoXmlPackedGroup& group = groups[depth]; - -#ifdef KOXML_COMPRESS - KoXmlPackedItem& item = group.newItem(); -#else - // reserve up front - if ((groups.size() % GROUP_GROW_SIZE) == 0) - group.reserve(GROUP_GROW_SIZE * (1 + (groups.size() >> GROUP_GROW_SHIFT))); - group.resize(group.count() + 1); - - KoXmlPackedItem& item = group[group.count()-1]; -#endif - - // this is necessary, because intentionally we don't want to have - // a constructor for KoXmlPackedItem - item.attr = false; - item.type = KoXmlNode::NullNode; - item.qnameIndex = 0; - item.childStart = itemCount(depth + 1); - item.value.clear(); - - return item; - } - - void clear() { - currentDepth = 0; - qnameHash.clear(); - qnameList.clear(); - valueHash.clear(); - valueList.clear(); - groups.clear(); - docType.clear(); - - // first node is root - KoXmlPackedItem& rootItem = newItem(0); - rootItem.type = KoXmlNode::DocumentNode; - } - - void finish() { - // won't be needed anymore - qnameHash.clear(); - valueHash.clear(); - valueList.clear(); - - // optimize, see documentation on QVector::squeeze - for (int d = 0; d < groups.count(); ++d) { - KoXmlPackedGroup& group = groups[d]; - group.squeeze(); - } - } - - // in case namespace processing, 'name' contains the prefix already - void addElement(const QString& name, const QString& nsURI) { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.type = KoXmlNode::ElementNode; - item.qnameIndex = cacheQName(name, nsURI); - - ++currentDepth; - } - - void closeElement() { - --currentDepth; - } - - void addDTD(const QString& dt) { - docType = dt; - } - - void addAttribute(const QString& name, const QString& nsURI, const QString& value) { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.attr = true; - item.qnameIndex = cacheQName(name, nsURI); - //item.value = cacheValue( value ); - item.value = value; - } - - void addText(const QString& text) { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.type = KoXmlNode::TextNode; - item.value = text; - } - - void addCData(const QString& text) { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.type = KoXmlNode::CDATASectionNode; - item.value = text; - } - - void addProcessingInstruction() { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.type = KoXmlNode::ProcessingInstructionNode; - } - -public: - KoXmlPackedDocument(): processNamespace(false), currentDepth(0) { - clear(); - } - -#else - -private: - unsigned elementDepth; - -public: - - KoXmlPackedItem& newItem() { - unsigned count = items.count() + 512; - count = 1024 * (count >> 10); - items.reserve(count); - - items.resize(items.count() + 1); - - // this is necessary, because intentionally we don't want to have - // a constructor for KoXmlPackedItem - KoXmlPackedItem& item = items[items.count()-1]; - item.attr = false; - item.type = KoXmlNode::NullNode; - item.qnameIndex = 0; - item.depth = 0; - - return item; - } - - void addElement(const QString& name, const QString& nsURI) { - // we are going one level deeper - ++elementDepth; - - KoXmlPackedItem& item = newItem(); - - item.attr = false; - item.type = KoXmlNode::ElementNode; - item.depth = elementDepth; - item.qnameIndex = cacheQName(name, nsURI); - } - - void closeElement() { - // we are going up one level - --elementDepth; - } - - void addDTD(const QString& dt) { - docType = dt; - } - - void addAttribute(const QString& name, const QString& nsURI, const QString& value) { - KoXmlPackedItem& item = newItem(); - - item.attr = true; - item.type = KoXmlNode::NullNode; - item.depth = elementDepth; - item.qnameIndex = cacheQName(name, nsURI); - //item.value = cacheValue( value ); - item.value = value; - } - - void addText(const QString& str) { - KoXmlPackedItem& item = newItem(); - - item.attr = false; - item.type = KoXmlNode::TextNode; - item.depth = elementDepth + 1; - item.qnameIndex = 0; - item.value = str; - } - - void addCData(const QString& str) { - KoXmlPackedItem& item = newItem(); - - item.attr = false; - item.type = KoXmlNode::CDATASectionNode; - item.depth = elementDepth + 1; - item.qnameIndex = 0; - item.value = str; - } - - void addProcessingInstruction() { - KoXmlPackedItem& item = newItem(); - - item.attr = false; - item.type = KoXmlNode::ProcessingInstructionNode; - item.depth = elementDepth + 1; - item.qnameIndex = 0; - item.value.clear(); - } - - void clear() { - qnameHash.clear(); - qnameList.clear(); - valueHash.clear(); - valueList.clear(); - items.clear(); - elementDepth = 0; - - KoXmlPackedItem& rootItem = newItem(); - rootItem.attr = false; - rootItem.type = KoXmlNode::DocumentNode; - rootItem.depth = 0; - rootItem.qnameIndex = 0; - } - - void finish() { - qnameHash.clear(); - valueList.clear(); - valueHash.clear(); - items.squeeze(); - } - - KoXmlPackedDocument(): processNamespace(false), elementDepth(0) { - } - -#endif - -}; - -namespace { - - class ParseError { - public: - QString errorMsg; - int errorLine; - int errorColumn; - bool error; - - ParseError() :errorLine(-1), errorColumn(-1), error(false) {} - }; - - void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true); - - // parse one element as if this were a standalone xml document - ParseError parseDocument(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true) - { - doc.clear(); - ParseError error; - xml.readNext(); - while (!xml.atEnd() && xml.tokenType() != QXmlStreamReader::EndDocument && !xml.hasError()) { - switch (xml.tokenType()) { - case QXmlStreamReader::StartElement: - parseElement(xml, doc, stripSpaces); - break; - case QXmlStreamReader::DTD: - doc.addDTD(xml.dtdName().toString()); - break; - case QXmlStreamReader::StartDocument: - if (!xml.documentEncoding().isEmpty() || !xml.documentVersion().isEmpty()) { - doc.addProcessingInstruction(); - } - break; - case QXmlStreamReader::ProcessingInstruction: - doc.addProcessingInstruction(); - break; - default: - break; - } - xml.readNext(); - } - if (xml.hasError()) { - error.error = true; - error.errorMsg = xml.errorString(); - error.errorColumn = xml.columnNumber(); - error.errorLine = xml.lineNumber(); - } else { - doc.finish(); - } - return error; - } - - void parseElementContents(QXmlStreamReader &xml, KoXmlPackedDocument &doc) - { - xml.readNext(); - QString ws; - while (!xml.atEnd()) { - switch (xml.tokenType()) { - case QXmlStreamReader::EndElement: - // if an element contains only whitespace, put it in the dom - if (!ws.isEmpty()) { - doc.addText(ws); - } - return; - case QXmlStreamReader::StartElement: - // The whitespaces between > and < are also a text element - if (!ws.isEmpty()) { - doc.addText(ws); - ws.clear(); - } - // Do not strip spaces - parseElement(xml, doc, false); - break; - case QXmlStreamReader::Characters: - if (xml.isCDATA()) { - doc.addCData(xml.text().toString()); - } else if (!xml.isWhitespace()) { - doc.addText(xml.text().toString()); - } else { - ws += xml.text(); - } - break; - case QXmlStreamReader::ProcessingInstruction: - doc.addProcessingInstruction(); - break; - default: - break; - } - xml.readNext(); - } - } - - void parseElementContentsStripSpaces(QXmlStreamReader &xml, KoXmlPackedDocument &doc) - { - xml.readNext(); - QString ws; - bool sawElement = false; - while (!xml.atEnd()) { - switch (xml.tokenType()) { - case QXmlStreamReader::EndElement: - // if an element contains only whitespace, put it in the dom - if (!ws.isEmpty() && !sawElement) { - doc.addText(ws); - } - return; - case QXmlStreamReader::StartElement: - sawElement = true; - // Do strip spaces - parseElement(xml, doc, true); - break; - case QXmlStreamReader::Characters: - if (xml.isCDATA()) { - doc.addCData(xml.text().toString()); - } else if (!xml.isWhitespace()) { - doc.addText(xml.text().toString()); - } else if (!sawElement) { - ws += xml.text(); - } - break; - case QXmlStreamReader::ProcessingInstruction: - doc.addProcessingInstruction(); - break; - default: - break; - } - xml.readNext(); - } - } - - void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces) - { - // Unfortunately MSVC fails using QXmlStreamReader::const_iterator - // so we apply a for loop instead. https://bugreports.qt.io/browse/QTBUG-45368 - doc.addElement(xml.qualifiedName().toString(), - fixNamespace(xml.namespaceUri().toString())); - QXmlStreamAttributes attr = xml.attributes(); - for (int a = 0; a < attr.count(); a++) { - doc.addAttribute(attr[a].qualifiedName().toString(), - attr[a].namespaceUri().toString(), - attr[a].value().toString()); - } - if (stripSpaces) - parseElementContentsStripSpaces(xml, doc); - else - parseElementContents(xml, doc); - // reader.tokenType() is now QXmlStreamReader::EndElement - doc.closeElement(); - } -} - - -// ================================================================== -// -// KoXmlNodeData -// -// ================================================================== - -class KoXmlNodeData -{ -public: - - explicit KoXmlNodeData(unsigned long initialRefCount = 1); - ~KoXmlNodeData(); - - // generic properties - KoXmlNode::NodeType nodeType; - bool loaded; - -#ifdef KOXML_COMPACT - unsigned nodeDepth; -#endif - - QString tagName; - QString namespaceURI; - QString prefix; - QString localName; - - void ref() { - ++refCount; - } - void unref() { - if (!--refCount) { - delete this; - } - } - - // type information - QString nodeName() const; - - // for tree and linked-list - KoXmlNodeData* parent; - KoXmlNodeData* prev; - KoXmlNodeData* next; - KoXmlNodeData* first; - KoXmlNodeData* last; - - QString text(); - - // node manipulation - void clear(); - - // attributes - inline void setAttribute(const QString& name, const QString& value); - inline QString attribute(const QString& name, const QString& def) const; - inline bool hasAttribute(const QString& name) const; - inline void setAttributeNS(const QString& nsURI, const QString& name, const QString& value); - inline QString attributeNS(const QString& nsURI, const QString& name, const QString& def) const; - inline bool hasAttributeNS(const QString& nsURI, const QString& name) const; - inline void clearAttributes(); - inline QStringList attributeNames() const; - inline QList< QPair > attributeFullNames() const; - - - // for text and CDATA - QString data() const; - - // reference from within the packed doc - KoXmlPackedDocument* packedDoc; - unsigned long nodeIndex; - - // used when doing on-demand (re)parse - void loadChildren(int depth = 1); - void unloadChildren(); - - void dump(); - - static KoXmlNodeData null; - - // compatibility - void asQDomNode(QDomDocument& ownerDoc) const; - -private: - QHash attr; - QHash attrNS; - QString textData; - // reference counting - unsigned long refCount; - friend class KoXmlElement; -}; - -KoXmlNodeData KoXmlNodeData::null; - - -KoXmlNodeData::KoXmlNodeData(unsigned long initialRefCount) - : nodeType(KoXmlNode::NullNode) - , loaded(false) -#ifdef KOXML_COMPACT - , nodeDepth(0) -#endif - , parent(0), prev(0), next(0), first(0), last(0) - , packedDoc(0), nodeIndex(0) - , refCount(initialRefCount) -{ -} - -KoXmlNodeData::~KoXmlNodeData() -{ - clear(); -} - -void KoXmlNodeData::clear() -{ - if (first) - for (KoXmlNodeData* node = first; node ;) { - KoXmlNodeData* next = node->next; - node->unref(); - node = next; - } - - // only document can delete these - // normal nodes don't "own" them - if (nodeType == KoXmlNode::DocumentNode) - delete packedDoc; - - nodeType = KoXmlNode::NullNode; - tagName.clear(); - prefix.clear(); - namespaceURI.clear(); - textData.clear(); - packedDoc = 0; - - attr.clear(); - attrNS.clear(); - - parent = 0; - prev = next = 0; - first = last = 0; - - loaded = false; -} - -QString KoXmlNodeData::text() -{ - QString t; - - loadChildren(); - - KoXmlNodeData* node = first; - while (node) { - switch (node->nodeType) { - case KoXmlNode::ElementNode: - t += node->text(); break; - case KoXmlNode::TextNode: - t += node->data(); break; - case KoXmlNode::CDATASectionNode: - t += node->data(); break; - default: break; - } - node = node->next; - } - - return t; -} - -QString KoXmlNodeData::nodeName() const -{ - switch (nodeType) { - case KoXmlNode::ElementNode: { - QString n(tagName); - if (!prefix.isEmpty()) - n.prepend(':').prepend(prefix); - return n; - } - break; - - case KoXmlNode::TextNode: return QLatin1String("#text"); - case KoXmlNode::CDATASectionNode: return QLatin1String("#cdata-section"); - case KoXmlNode::DocumentNode: return QLatin1String("#document"); - case KoXmlNode::DocumentTypeNode: return tagName; - - default: return QString(); break; - } - - // should not happen - return QString(); -} - -void KoXmlNodeData::setAttribute(const QString& name, const QString& value) -{ - attr.insert(name, value); -} - -QString KoXmlNodeData::attribute(const QString& name, const QString& def) const -{ - return attr.value(name, def); -} - -bool KoXmlNodeData::hasAttribute(const QString& name) const -{ - return attr.contains(name); -} - -void KoXmlNodeData::setAttributeNS(const QString& nsURI, - const QString& name, const QString& value) -{ - int i = name.indexOf(':'); - if (i != -1) { - QString localName(name.mid(i + 1)); - KoXmlStringPair key(nsURI, localName); - attrNS.insert(key, value); - } -} - -QString KoXmlNodeData::attributeNS(const QString& nsURI, const QString& name, - const QString& def) const -{ - KoXmlStringPair key(nsURI, name); - return attrNS.value(key, def); -} - -bool KoXmlNodeData::hasAttributeNS(const QString& nsURI, const QString& name) const -{ - KoXmlStringPair key(nsURI, name); - return attrNS.contains(key); -} - -void KoXmlNodeData::clearAttributes() -{ - attr.clear(); - attrNS.clear(); -} - -// FIXME how about namespaced attributes ? -QStringList KoXmlNodeData::attributeNames() const -{ - QStringList result; - result = attr.keys(); - - return result; -} - - -QList< QPair > KoXmlNodeData::attributeFullNames() const -{ - QList< QPair > result; - result = attrNS.keys(); - - return result; -} - -QString KoXmlNodeData::data() const -{ - return textData; -} - -#ifdef KOXML_COMPACT - -void KoXmlNodeData::loadChildren(int depth) -{ - // sanity check - if (!packedDoc) return; - - // already loaded ? - if (loaded && (depth <= 1)) return; - - // in case depth is different - unloadChildren(); - - - KoXmlNodeData* lastDat = 0; - - unsigned childStop = 0; - if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1) - childStop = packedDoc->itemCount(nodeDepth + 1); - else { - const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1); - childStop = next.childStart; - } - - const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex); - - for (unsigned i = self.childStart; i < childStop; ++i) { - const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i); - bool textItem = (item.type == KoXmlNode::TextNode); - textItem |= (item.type == KoXmlNode::CDATASectionNode); - - // attribute belongs to this node - if (item.attr) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - QString value = item.value; - - QString prefix; - - QString qName; // with prefix - QString localName; // without prefix, i.e. local name - - localName = qName = qname.name; - int i = qName.indexOf(':'); - if (i != -1) prefix = qName.left(i); - if (i != -1) localName = qName.mid(i + 1); - - if (packedDoc->processNamespace) { - setAttributeNS(qname.nsURI, qName, value); - setAttribute(localName, value); - } else - setAttribute(qName, value); - } else { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - QString value = item.value; - - QString nodeName = qname.name; - QString localName; - QString prefix; - - if (packedDoc->processNamespace) { - localName = qname.name; - int di = qname.name.indexOf(':'); - if (di != -1) { - localName = qname.name.mid(di + 1); - prefix = qname.name.left(di); - } - nodeName = localName; - } - - // make a node out of this item - KoXmlNodeData* dat = new KoXmlNodeData; - dat->nodeIndex = i; - dat->packedDoc = packedDoc; - dat->nodeDepth = nodeDepth + 1; - dat->nodeType = item.type; - dat->tagName = nodeName; - dat->localName = localName; - dat->prefix = prefix; - dat->namespaceURI = qname.nsURI; - dat->parent = this; - dat->prev = lastDat; - dat->next = 0; - dat->first = 0; - dat->last = 0; - dat->loaded = false; - dat->textData = (textItem) ? value : QString(); - - // adjust our linked-list - first = (first) ? first : dat; - last = dat; - if (lastDat) - lastDat->next = dat; - lastDat = dat; - - // recursive - if (depth > 1) - dat->loadChildren(depth - 1); - } - } - - loaded = true; -} - -#else - -void KoXmlNodeData::loadChildren(int depth) -{ - // sanity check - if (!packedDoc) return; - - // already loaded ? - if (loaded && (depth <= 1)) return; - - // cause we don't know how deep this node's children already loaded are - unloadChildren(); - - KoXmlNodeData* lastDat = 0; - int nodeDepth = packedDoc->items[nodeIndex].depth; - - for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) { - KoXmlPackedItem& item = packedDoc->items[i]; - bool textItem = (item.type == KoXmlNode::TextNode); - textItem |= (item.type == KoXmlNode::CDATASectionNode); - - // element already outside our depth - if (!item.attr && (item.type == KoXmlNode::ElementNode)) - if (item.depth <= (unsigned)nodeDepth) - break; - - // attribute belongs to this node - if (item.attr && (item.depth == (unsigned)nodeDepth)) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - QString value = item.value; - - QString prefix; - - QString qName; // with prefix - QString localName; // without prefix, i.e. local name - - localName = qName = qname.name; - int i = qName.indexOf(':'); - if (i != -1) prefix = qName.left(i); - if (i != -1) localName = qName.mid(i + 1); - - if (packedDoc->processNamespace) { - setAttributeNS(qname.nsURI, qName, value); - setAttribute(localName, value); - } else - setAttribute(qname.name, value); - } - - // the child node - if (!item.attr) { - bool instruction = (item.type == KoXmlNode::ProcessingInstructionNode); - bool ok = (textItem || instruction) ? (item.depth == (unsigned)nodeDepth) : (item.depth == (unsigned)nodeDepth + 1); - - ok = (item.depth == (unsigned)nodeDepth + 1); - - if (ok) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - QString value = item.value; - - QString nodeName = qname.name; - QString localName; - QString prefix; - - if (packedDoc->processNamespace) { - localName = qname.name; - int di = qname.name.indexOf(':'); - if (di != -1) { - localName = qname.name.mid(di + 1); - prefix = qname.name.left(di); - } - nodeName = localName; - } - - // make a node out of this item - KoXmlNodeData* dat = new KoXmlNodeData; - dat->nodeIndex = i; - dat->packedDoc = packedDoc; - dat->nodeType = item.type; - dat->tagName = nodeName; - dat->localName = localName; - dat->prefix = prefix; - dat->namespaceURI = qname.nsURI; - dat->count = 1; - dat->parent = this; - dat->prev = lastDat; - dat->next = 0; - dat->first = 0; - dat->last = 0; - dat->loaded = false; - dat->textData = (textItem) ? value : QString(); - - // adjust our linked-list - first = (first) ? first : dat; - last = dat; - if (lastDat) - lastDat->next = dat; - lastDat = dat; - - // recursive - if (depth > 1) - dat->loadChildren(depth - 1); - } - } - } - - loaded = true; -} -#endif - -void KoXmlNodeData::unloadChildren() -{ - // sanity check - if (!packedDoc) return; - - if (!loaded) return; - - if (first) - for (KoXmlNodeData* node = first; node ;) { - KoXmlNodeData* next = node->next; - node->unloadChildren(); - node->unref(); - node = next; - } - - clearAttributes(); - loaded = false; - first = last = 0; -} - -#ifdef KOXML_COMPACT - - -static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc, - unsigned nodeDepth, unsigned nodeIndex, QDomNode parentNode = QDomNode()) -{ - // sanity check - if (!packedDoc) - return; - - const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex); - - unsigned childStop = 0; - if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1) - childStop = packedDoc->itemCount(nodeDepth + 1); - else { - const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1); - childStop = next.childStart; - } - - // nothing to do here - if (self.type == KoXmlNode::NullNode) - return; - - // create the element properly - if (self.type == KoXmlNode::ElementNode) { - QDomElement element; - - KoQName qname = packedDoc->qnameList[self.qnameIndex]; - qname.nsURI = fixNamespace(qname.nsURI); - - if (packedDoc->processNamespace) - element = ownerDoc.createElementNS(qname.nsURI, qname.name); - else - element = ownerDoc.createElement(qname.name); - - if ( parentNode.isNull() ) { - ownerDoc.appendChild( element ); - } else { - parentNode.appendChild( element ); - } - // check all subnodes for attributes - for (unsigned i = self.childStart; i < childStop; ++i) { - const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i); - bool textItem = (item.type == KoXmlNode::TextNode); - textItem |= (item.type == KoXmlNode::CDATASectionNode); - - // attribute belongs to this node - if (item.attr) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - qname.nsURI = fixNamespace(qname.nsURI ); - QString value = item.value; - - QString prefix; - - QString qName; // with prefix - QString localName; // without prefix, i.e. local name - - localName = qName = qname.name; - int i = qName.indexOf(':'); - if (i != -1) prefix = qName.left(i); - if (i != -1) localName = qName.mid(i + 1); - - if (packedDoc->processNamespace) { - element.setAttributeNS(qname.nsURI, qName, value); - element.setAttribute(localName, value); - } else - element.setAttribute(qname.name, value); - } else { - // add it recursively - itemAsQDomNode(ownerDoc, packedDoc, nodeDepth + 1, i, element); - } - } - return; - } - - // create the text node - if (self.type == KoXmlNode::TextNode) { - QString text = self.value; - - // FIXME: choose CDATA when the value contains special characters - QDomText textNode = ownerDoc.createTextNode(text); - if ( parentNode.isNull() ) { - ownerDoc.appendChild( textNode ); - } else { - parentNode.appendChild( textNode ); - } - return; - } - // nothing matches? strange... -} - -void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const -{ - itemAsQDomNode(ownerDoc, packedDoc, nodeDepth, nodeIndex); -} - -#else - -static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc, - unsigned nodeIndex, QDomNode parentNode = QDomNode()) -{ - // sanity check - if (!packedDoc) - return; - - KoXmlPackedItem& item = packedDoc->items[nodeIndex]; - - // nothing to do here - if (item.type == KoXmlNode::NullNode) - return; - - // create the element properly - if (item.type == KoXmlNode::ElementNode) { - QDomElement element; - - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - qname.nsURI = fixNamespace(qname.nsURI); - - if (packedDoc->processNamespace) - element = ownerDoc.createElementNS(qname.nsURI, qname.name); - else - element = ownerDoc.createElement(qname.name); - - if ( parentNode.isNull() ) { - ownerDoc.appendChild( element ); - } else { - parentNode.appendChild( element ); - } - // check all subnodes for attributes - int nodeDepth = item.depth; - for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) { - KoXmlPackedItem& item = packedDoc->items[i]; - bool textItem = (item.type == KoXmlNode::TextNode); - textItem |= (item.type == KoXmlNode::CDATASectionNode); - - // element already outside our depth - if (!item.attr && (item.type == KoXmlNode::ElementNode)) - if (item.depth <= (unsigned)nodeDepth) - break; - - // attribute belongs to this node - if (item.attr && (item.depth == (unsigned)nodeDepth)) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - qname.nsURI = fixNamespace(qname.nsURI); - QString value = item.value; - QString prefix; - - QString qName; // with prefix - QString localName; // without prefix, i.e. local name - - localName = qName = qname.name; - int i = qName.indexOf(':'); - if (i != -1) prefix = qName.left(i); - if (i != -1) localName = qName.mid(i + 1); - - if (packedDoc->processNamespace) { - element.setAttributeNS(qname.nsURI, qName, value); - element.setAttribute(localName, value); - } else - element.setAttribute(qname.name, value); - } - - // direct child of this node - if (!item.attr && (item.depth == (unsigned)nodeDepth + 1)) { - // add it recursively - itemAsQDomNode(ownerDoc, packedDoc, i, element); - } - } - return; - } - - // create the text node - if (item.type == KoXmlNode::TextNode) { - QString text = item.value; - // FIXME: choose CDATA when the value contains special characters - QDomText textNode = ownerDoc.createTextNode(text); - if ( parentNode.isNull() ) { - ownerDoc.appendChild( textNode ); - } else { - parentNode.appendChild( textNode ); - } - return; - } - - // nothing matches? strange... -} - -void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const -{ - itemAsQDomNode(ownerDoc, packedDoc, nodeIndex); -} - -#endif - -void KoXmlNodeData::dump() -{ - printf("NodeData %p\n", (void*)this); - - printf(" nodeIndex: %d\n", (int)nodeIndex); - printf(" packedDoc: %p\n", (void*)packedDoc); - - printf(" nodeType : %d\n", (int)nodeType); - printf(" tagName: %s\n", qPrintable(tagName)); - printf(" namespaceURI: %s\n", qPrintable(namespaceURI)); - printf(" prefix: %s\n", qPrintable(prefix)); - printf(" localName: %s\n", qPrintable(localName)); - - printf(" parent : %p\n", (void*)parent); - printf(" prev : %p\n", (void*)prev); - printf(" next : %p\n", (void*)next); - printf(" first : %p\n", (void*)first); - printf(" last : %p\n", (void*)last); - - printf(" refCount: %ld\n", refCount); - - if (loaded) - printf(" loaded: TRUE\n"); - else - printf(" loaded: FALSE\n"); -} - - -// ================================================================== -// -// KoXmlNodeData -// -// ================================================================== - -class KoXmlDocumentData : public KoXmlNodeData -{ -public: - - KoXmlDocumentData(unsigned long initialRefCount = 1); - ~KoXmlDocumentData(); - - bool setContent(QXmlStreamReader *reader, - QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); - - KoXmlDocumentType dt; - - bool emptyDocument :1; - // to read the xml with or without spaces - bool stripSpaces :1; -}; - -#define KOXMLDOCDATA(d) static_cast(d) - - -KoXmlDocumentData::KoXmlDocumentData(unsigned long initialRefCount) - : KoXmlNodeData(initialRefCount) - , emptyDocument(true) - , stripSpaces(true) -{ -} - -KoXmlDocumentData::~KoXmlDocumentData() -{ -} - -bool KoXmlDocumentData::setContent(QXmlStreamReader* reader, QString* errorMsg, int* errorLine, int* errorColumn) -{ - // sanity checks - if (!reader) return false; - - if (nodeType != KoXmlNode::DocumentNode) - return false; - - clear(); - nodeType = KoXmlNode::DocumentNode; - - packedDoc = new KoXmlPackedDocument; - packedDoc->processNamespace = reader->namespaceProcessing(); - - ParseError error = parseDocument(*reader, *packedDoc, stripSpaces); - if (error.error) { - // parsing error has occurred - if (errorMsg) *errorMsg = error.errorMsg; - if (errorLine) *errorLine = error.errorLine; - if (errorColumn) *errorColumn = error.errorColumn; - return false; - } - - // initially load - loadChildren(); - - KoXmlNodeData *typeData = new KoXmlNodeData(0); - typeData->nodeType = KoXmlNode::DocumentTypeNode; - typeData->tagName = packedDoc->docType; - typeData->parent = this; - dt = KoXmlDocumentType(typeData); - - return true; -} - -// ================================================================== -// -// KoXmlNode -// -// ================================================================== - -// Creates a null node -KoXmlNode::KoXmlNode() -{ - d = &KoXmlNodeData::null; - d->ref(); -} - -// Destroys this node -KoXmlNode::~KoXmlNode() -{ - d->unref(); -} - -// Creates a copy of another node -KoXmlNode::KoXmlNode(const KoXmlNode& node) -{ - d = node.d; - d->ref(); -} - -// Creates a node for specific implementation -KoXmlNode::KoXmlNode(KoXmlNodeData* data) -{ - d = data; - data->ref(); -} - -// Creates a shallow copy of another node -KoXmlNode& KoXmlNode::operator=(const KoXmlNode & node) -{ - if (this != &node) { - d->unref(); - d = node.d; - d->ref(); - } - return *this; -} - -// Note: two null nodes are always equal -bool KoXmlNode::operator==(const KoXmlNode& node) const -{ - if (isNull() && node.isNull()) return true; - return(d == node.d); -} - -// Note: two null nodes are always equal -bool KoXmlNode::operator!=(const KoXmlNode& node) const -{ - if (isNull() && !node.isNull()) return true; - if (!isNull() && node.isNull()) return true; - if (isNull() && node.isNull()) return false; - return(d != node.d); -} - -KoXmlNode::NodeType KoXmlNode::nodeType() const -{ - return d->nodeType; -} - -bool KoXmlNode::isNull() const -{ - return d->nodeType == NullNode; -} - -bool KoXmlNode::isElement() const -{ - return d->nodeType == ElementNode; -} - -bool KoXmlNode::isText() const -{ - return (d->nodeType == TextNode) || isCDATASection(); -} - -bool KoXmlNode::isCDATASection() const -{ - return d->nodeType == CDATASectionNode; -} - -bool KoXmlNode::isDocument() const -{ - return d->nodeType == DocumentNode; -} - -bool KoXmlNode::isDocumentType() const -{ - return d->nodeType == DocumentTypeNode; -} - -void KoXmlNode::clear() -{ - d->unref(); - d = new KoXmlNodeData; -} - -QString KoXmlNode::nodeName() const -{ - return d->nodeName(); -} - -QString KoXmlNode::prefix() const -{ - return isElement() ? d->prefix : QString(); -} - -QString KoXmlNode::namespaceURI() const -{ - return isElement() ? d->namespaceURI : QString(); -} - -QString KoXmlNode::localName() const -{ - return isElement() ? d->localName : QString(); -} - -KoXmlDocument KoXmlNode::ownerDocument() const -{ - KoXmlNodeData* node = d; - while (node->parent) node = node->parent; - - if (node->nodeType == DocumentNode) { - return KoXmlDocument(static_cast(node)); - } - return KoXmlDocument(); -} - -KoXmlNode KoXmlNode::parentNode() const -{ - return d->parent ? KoXmlNode(d->parent) : KoXmlNode(); -} - -bool KoXmlNode::hasChildNodes() const -{ - if (isText()) - return false; - - if (!d->loaded) - d->loadChildren(); - - return d->first != 0 ; -} - -int KoXmlNode::childNodesCount() const -{ - if (isText()) - return 0; - - if (!d->loaded) - d->loadChildren(); - - KoXmlNodeData* node = d->first; - int count = 0; - while (node) { - ++count; - node = node->next; - } - - return count; -} - -QStringList KoXmlNode::attributeNames() const -{ - if (!d->loaded) - d->loadChildren(); - - return d->attributeNames(); -} - -QList< QPair > KoXmlNode::attributeFullNames() const -{ - if (!d->loaded) - d->loadChildren(); - - return d->attributeFullNames(); -} - -KoXmlNode KoXmlNode::firstChild() const -{ - if (!d->loaded) - d->loadChildren(); - return d->first ? KoXmlNode(d->first) : KoXmlNode(); -} - -KoXmlElement KoXmlNode::firstChildElement() const -{ - KoXmlElement element; - forEachElement (element, (*this)) { - return element; - } - return KoXmlElement(); -} - -KoXmlNode KoXmlNode::lastChild() const -{ - if (!d->loaded) - d->loadChildren(); - return d->last ? KoXmlNode(d->last) : KoXmlNode(); -} - -KoXmlNode KoXmlNode::nextSibling() const -{ - return d->next ? KoXmlNode(d->next) : KoXmlNode(); -} - -KoXmlNode KoXmlNode::previousSibling() const -{ - return d->prev ? KoXmlNode(d->prev) : KoXmlNode(); -} - -KoXmlNode KoXmlNode::namedItem(const QString& name) const -{ - if (!d->loaded) - d->loadChildren(); - - for (KoXmlNodeData* node = d->first; node; node = node->next) { - if (node->nodeName() == name) - return KoXmlNode(node); - } - - // not found - return KoXmlNode(); -} - -KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name) const -{ - if (!d->loaded) - d->loadChildren(); - - for (KoXmlNodeData* node = d->first; node; node = node->next) { - if (node->nodeType == KoXmlNode::ElementNode - && node->localName == name - && node->namespaceURI == nsURI - ) { - return KoXmlNode(node); - } - } - - // not found - return KoXmlNode(); -} - -KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const -{ - if (!d->loaded) - d->loadChildren(); - - for (KoXmlNodeData* node = d->first; node; node = node->next) { - if (node->nodeType != KoXmlNode::ElementNode) - continue; - if (node->localName == name && node->namespaceURI == nsURI) { - return KoXmlNode(node); - } - bool isPrelude = false; - switch (type) { - case KoXmlTextContentPrelude: - isPrelude = - (node->localName == "tracked-changes" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "variable-decls" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "user-field-decls" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "user-field-decl" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "sequence-decls" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "sequence-decl" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "dde-connection-decls" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "alphabetical-index-auto-mark-file" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "forms" && node->namespaceURI == KoXmlNS::office); - break; - } - if (!isPrelude) { - return KoXmlNode(); // no TextContentPrelude means it follows TextContentMain, so stop here. - } - } - - // not found - return KoXmlNode(); -} - -KoXmlElement KoXmlNode::toElement() const -{ - return isElement() ? KoXmlElement(d) : KoXmlElement(); -} - -KoXmlText KoXmlNode::toText() const -{ - return isText() ? KoXmlText(d) : KoXmlText(); -} - -KoXmlCDATASection KoXmlNode::toCDATASection() const -{ - return isCDATASection() ? KoXmlCDATASection(d) : KoXmlCDATASection(); -} - -KoXmlDocument KoXmlNode::toDocument() const -{ - if (isDocument()) { - return KoXmlDocument(static_cast(d)); - } - return KoXmlDocument(); -} - -void KoXmlNode::load(int depth) -{ - d->loadChildren(depth); -} - -void KoXmlNode::unload() -{ - d->unloadChildren(); -} - -void KoXmlNode::asQDomNode(QDomDocument& ownerDoc) const -{ - Q_ASSERT(!isDocument()); - d->asQDomNode(ownerDoc); -} - -// ================================================================== -// -// KoXmlElement -// -// ================================================================== - -// Creates an empty element -KoXmlElement::KoXmlElement(): KoXmlNode() -{ -} - -KoXmlElement::~KoXmlElement() -{ -} - -// Creates a shallow copy of another element -KoXmlElement::KoXmlElement(const KoXmlElement& element): KoXmlNode(element.d) -{ -} - -KoXmlElement::KoXmlElement(KoXmlNodeData* data): KoXmlNode(data) -{ -} - -// Copies another element -KoXmlElement& KoXmlElement::operator=(const KoXmlElement & element) -{ - KoXmlNode::operator=(element); - return *this; -} - -bool KoXmlElement::operator== (const KoXmlElement& element) const -{ - if (isNull() || element.isNull()) return false; - return (d == element.d); -} - -bool KoXmlElement::operator!= (const KoXmlElement& element) const -{ - if (isNull() && element.isNull()) return false; - if (isNull() || element.isNull()) return true; - return (d != element.d); -} - -QString KoXmlElement::tagName() const -{ - return isElement() ? d->tagName : QString(); -} - -QString KoXmlElement::text() const -{ - return d->text(); -} - -QString KoXmlElement::attribute(const QString& name) const -{ - if (!isElement()) - return QString(); - - if (!d->loaded) - d->loadChildren(); - - return d->attribute(name, QString()); -} - -QString KoXmlElement::attribute(const QString& name, - const QString& defaultValue) const -{ - if (!isElement()) - return defaultValue; - - if (!d->loaded) - d->loadChildren(); - - return d->attribute(name, defaultValue); -} - -QString KoXmlElement::attributeNS(const QString& namespaceURI, - const QString& localName, const QString& defaultValue) const -{ - if (!isElement()) - return defaultValue; - - if (!d->loaded) - d->loadChildren(); - - KoXmlStringPair key(namespaceURI, localName); - return d->attrNS.value(key, defaultValue); - -// return d->attributeNS( namespaceURI, localName, defaultValue ); -} - -bool KoXmlElement::hasAttribute(const QString& name) const -{ - if (!d->loaded) - d->loadChildren(); - - return isElement() ? d->hasAttribute(name) : false; -} - -bool KoXmlElement::hasAttributeNS(const QString& namespaceURI, - const QString& localName) const -{ - if (!d->loaded) - d->loadChildren(); - - return isElement() ? d->hasAttributeNS(namespaceURI, localName) : false; -} - -// ================================================================== -// -// KoXmlText -// -// ================================================================== - -KoXmlText::KoXmlText(): KoXmlNode() -{ -} - -KoXmlText::~KoXmlText() -{ -} - -KoXmlText::KoXmlText(const KoXmlText& text): KoXmlNode(text.d) -{ -} - -KoXmlText::KoXmlText(KoXmlNodeData* data): KoXmlNode(data) -{ -} - -bool KoXmlText::isText() const -{ - return true; -} - -QString KoXmlText::data() const -{ - return d->data(); -} - -KoXmlText& KoXmlText::operator=(const KoXmlText & element) -{ - KoXmlNode::operator=(element); - return *this; -} - -// ================================================================== -// -// KoXmlCDATASection -// -// ================================================================== - -KoXmlCDATASection::KoXmlCDATASection(): KoXmlText() -{ -} - -KoXmlCDATASection::KoXmlCDATASection(const KoXmlCDATASection& cdata) - : KoXmlText(cdata) -{ -} - -KoXmlCDATASection::~KoXmlCDATASection() -{ -} - -KoXmlCDATASection::KoXmlCDATASection(KoXmlNodeData* cdata): - KoXmlText(cdata) -{ -} - -bool KoXmlCDATASection::isCDATASection() const -{ - return true; -} - -KoXmlCDATASection& KoXmlCDATASection::operator=(const KoXmlCDATASection & cdata) -{ - KoXmlNode::operator=(cdata); - return *this; -} - -// ================================================================== -// -// KoXmlDocumentType -// -// ================================================================== - -KoXmlDocumentType::KoXmlDocumentType(): KoXmlNode() -{ -} - -KoXmlDocumentType::~KoXmlDocumentType() -{ -} - -KoXmlDocumentType::KoXmlDocumentType(const KoXmlDocumentType& dt): - KoXmlNode(dt.d) -{ -} - -QString KoXmlDocumentType::name() const -{ - return nodeName(); -} - -KoXmlDocumentType::KoXmlDocumentType(KoXmlNodeData* dt): KoXmlNode(dt) -{ -} - -KoXmlDocumentType& KoXmlDocumentType::operator=(const KoXmlDocumentType & dt) -{ - KoXmlNode::operator=(dt); - return *this; -} - -// ================================================================== -// -// KoXmlDocument -// -// ================================================================== - -KoXmlDocument::KoXmlDocument(bool stripSpaces): KoXmlNode(new KoXmlDocumentData(0)) -{ - KOXMLDOCDATA(d)->emptyDocument = false; - KOXMLDOCDATA(d)->stripSpaces = stripSpaces; -} - -KoXmlDocument::~KoXmlDocument() -{ -} - -KoXmlDocument::KoXmlDocument(KoXmlDocumentData* data): KoXmlNode(data) -{ - KOXMLDOCDATA(d)->emptyDocument = true; -} - -// Creates a copy of another document -KoXmlDocument::KoXmlDocument(const KoXmlDocument& doc): KoXmlNode(doc.d) -{ -} - -// Creates a shallow copy of another document -KoXmlDocument& KoXmlDocument::operator=(const KoXmlDocument & doc) -{ - KoXmlNode::operator=(doc); - return *this; -} - -// Checks if this document and doc are equals -bool KoXmlDocument::operator==(const KoXmlDocument& doc) const -{ - return(d == doc.d); -} - -// Checks if this document and doc are not equals -bool KoXmlDocument::operator!=(const KoXmlDocument& doc) const -{ - return(d != doc.d); -} - -KoXmlElement KoXmlDocument::documentElement() const -{ - if (!d->loaded) - d->loadChildren(); - - for (KoXmlNodeData* node = d->first; node; node = node->next) { - if (node->nodeType == KoXmlNode::ElementNode) { - return KoXmlElement(node); - } - } - - return KoXmlElement(); -} - -KoXmlDocumentType KoXmlDocument::doctype() const -{ - return KOXMLDOCDATA(d)->dt; -} - -QString KoXmlDocument::nodeName() const -{ - return (KOXMLDOCDATA(d)->emptyDocument) ? QString::fromLatin1("#document") : QString(); -} - -void KoXmlDocument::clear() -{ - d->unref(); - KoXmlDocumentData *dat = new KoXmlDocumentData; - dat->emptyDocument = false; - d = dat; -} - -namespace { - /* Use an entity resolver that ignores undefined entities and simply - returns an empty string for them. - */ - class DumbEntityResolver : public QXmlStreamEntityResolver { - public: - QString resolveUndeclaredEntity ( const QString &) override { return ""; } - }; - -} - -bool KoXmlDocument::setContent(QXmlStreamReader *reader, - QString* errorMsg, int* errorLine, int* errorColumn) -{ - if (d->nodeType != KoXmlNode::DocumentNode) { - const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; - d->unref(); - KoXmlDocumentData *dat = new KoXmlDocumentData; - dat->nodeType = KoXmlNode::DocumentNode; - dat->stripSpaces = stripSpaces; - d = dat; - } - - const bool result = KOXMLDOCDATA(d)->setContent(reader, errorMsg, errorLine, errorColumn); - - return result; -} - -// no namespace processing -bool KoXmlDocument::setContent(QIODevice* device, QString* errorMsg, - int* errorLine, int* errorColumn) -{ - return setContent(device, false, errorMsg, errorLine, errorColumn); -} - -bool KoXmlDocument::setContent(QIODevice* device, bool namespaceProcessing, - QString* errorMsg, int* errorLine, int* errorColumn) -{ - if (d->nodeType != KoXmlNode::DocumentNode) { - const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; - d->unref(); - KoXmlDocumentData *dat = new KoXmlDocumentData; - dat->nodeType = KoXmlNode::DocumentNode; - dat->stripSpaces = stripSpaces; - d = dat; - } - - if (!device->isOpen()) device->open(QIODevice::ReadOnly); - QXmlStreamReader reader(device); - reader.setNamespaceProcessing(namespaceProcessing); - DumbEntityResolver entityResolver; - reader.setEntityResolver(&entityResolver); - - const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn); - - return result; -} - -bool KoXmlDocument::setContent(const QByteArray& text, bool namespaceProcessing, - QString *errorMsg, int *errorLine, int *errorColumn) -{ - QBuffer buffer; - buffer.setData(text); - return setContent(&buffer, namespaceProcessing, errorMsg, errorLine, errorColumn); -} - -bool KoXmlDocument::setContent(const QString& text, bool namespaceProcessing, - QString *errorMsg, int *errorLine, int *errorColumn) -{ - if (d->nodeType != KoXmlNode::DocumentNode) { - const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; - d->unref(); - KoXmlDocumentData *dat = new KoXmlDocumentData; - dat->nodeType = KoXmlNode::DocumentNode; - dat->stripSpaces = stripSpaces; - d = dat; - } - - QXmlStreamReader reader(text); - reader.setNamespaceProcessing(namespaceProcessing); - DumbEntityResolver entityResolver; - reader.setEntityResolver(&entityResolver); - - const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn); - - return result; -} - -bool KoXmlDocument::setContent(const QString& text, - QString *errorMsg, int *errorLine, int *errorColumn) -{ - return setContent(text, false, errorMsg, errorLine, errorColumn); -} - -void KoXmlDocument::setWhitespaceStripping(bool stripSpaces) -{ - KOXMLDOCDATA(d)->stripSpaces = stripSpaces; -} - +#include +#include +#include -#endif // ================================================================== // // functions in KoXml namespace // // ================================================================== KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName) { -#ifdef KOXML_USE_QDOM // David's solution for namedItemNS, only for QDom stuff KoXmlNode n = node.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement() && n.localName() == localName && n.namespaceURI() == nsURI) return n.toElement(); } return KoXmlElement(); -#else - return node.namedItemNS(nsURI, localName).toElement(); -#endif } KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName, KoXmlNamedItemType type) { -#ifdef KOXML_USE_QDOM -Q_ASSERT(false); + Q_UNUSED(type) return namedItemNS(node, nsURI, localName); -#else - return node.namedItemNS(nsURI, localName, type).toElement(); -#endif } void KoXml::load(KoXmlNode& node, int depth) { -#ifdef KOXML_USE_QDOM // do nothing, QDom has no on-demand loading Q_UNUSED(node); Q_UNUSED(depth); -#else - node.load(depth); -#endif } void KoXml::unload(KoXmlNode& node) { -#ifdef KOXML_USE_QDOM // do nothing, QDom has no on-demand unloading Q_UNUSED(node); -#else - node.unload(); -#endif } int KoXml::childNodesCount(const KoXmlNode& node) { -#ifdef KOXML_USE_QDOM return node.childNodes().count(); -#else - // compatibility function, because no need to implement - // a class like QDomNodeList - return node.childNodesCount(); -#endif } QStringList KoXml::attributeNames(const KoXmlNode& node) { -#ifdef KOXML_USE_QDOM QStringList result; QDomNamedNodeMap attrMap = node.attributes(); for (int i = 0; i < attrMap.count(); ++i) result += attrMap.item(i).toAttr().name(); return result; -#else - // compatibility function, because no need to implement - // a class like QDomNamedNodeMap - return node.attributeNames(); -#endif } void KoXml::asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node) { Q_ASSERT(!node.isDocument()); -#ifdef KOXML_USE_QDOM - ownerDoc.appendChild(ownerDoc.importNode(node)); -#else - node.asQDomNode(ownerDoc); -#endif + ownerDoc.appendChild(ownerDoc.importNode(node, true)); } void KoXml::asQDomElement(QDomDocument &ownerDoc, const KoXmlElement& element) { KoXml::asQDomNode(ownerDoc, element); } QDomDocument KoXml::asQDomDocument(const KoXmlDocument& document) { -#ifdef KOXML_USE_QDOM return document; -#else - QDomDocument qdoc( document.nodeName() ); - if ( document.hasChildNodes() ) { - for ( KoXmlNode n = document.firstChild(); ! n.isNull(); n = n.nextSibling() ) { - KoXml::asQDomNode(qdoc, n); - } - } - return qdoc; -#endif } -bool KoXml::setDocument(KoXmlDocument& doc, QIODevice* device, - bool namespaceProcessing, QString* errorMsg, int* errorLine, - int* errorColumn) +bool KoXml::setDocument(KoXmlDocument& doc, QIODevice *device, + bool namespaceProcessing, + QString *errorMsg, int *errorLine, int *errorColumn) { - QXmlStreamReader reader(device); - reader.setNamespaceProcessing(namespaceProcessing); - bool result = doc.setContent(&reader, errorMsg, errorLine, errorColumn); + bool result = doc.setContent(device, namespaceProcessing, errorMsg, errorLine, errorColumn); return result; } diff --git a/libs/store/KoXmlReader.h b/libs/store/KoXmlReader.h index 1a39f6762e..aa0f888348 100644 --- a/libs/store/KoXmlReader.h +++ b/libs/store/KoXmlReader.h @@ -1,451 +1,181 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat 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 KO_XMLREADER_H #define KO_XMLREADER_H -// KOXML_USE_QDOM is defined there #include "KoXmlReaderForward.h" #include "kritastore_export.h" #include #include class QIODevice; -#ifndef KOXML_USE_QDOM - -class QXmlStreamReader; - -class KoXmlNodeData; -class KoXmlDocumentData; -class QDomDocument; -class QStringList; /** * The office-text-content-prelude type. */ enum KoXmlNamedItemType { KoXmlTextContentPrelude ///< office-text-content-prelude //KoXmlTextContentMain, ///< office-text-content-main //KoXmlTextContentEpilogue ///< office-text-content-epilogue }; -/** -* KoXmlNode represents a node in a DOM tree. -* -* KoXmlNode is a base class for KoXmlElement, KoXmlText. -* Often, these subclasses are used for getting the data instead of KoXmlNode. -* However, as base class, KoXmlNode is very helpful when for example iterating -* all child nodes within one parent node. -* -* KoXmlNode implements an explicit sharing, a node shares its data with -* other copies (if exist). -* -* XXX: DO NOT ADD CONVENIENCE API HERE BECAUSE THIS CLASS MUST REMAIN COMPATIBLE WITH QDOMNODE! -* -* @author Ariya Hidayat -*/ -class KRITASTORE_EXPORT KoXmlNode -{ -public: - - enum NodeType { - NullNode = 0, - ElementNode, - TextNode, - CDATASectionNode, - ProcessingInstructionNode, - DocumentNode, - DocumentTypeNode - }; - - KoXmlNode(); - KoXmlNode(const KoXmlNode& node); - KoXmlNode& operator=(const KoXmlNode& node); - bool operator== (const KoXmlNode&) const; - bool operator!= (const KoXmlNode&) const; - virtual ~KoXmlNode(); - - virtual KoXmlNode::NodeType nodeType() const; - virtual bool isNull() const; - virtual bool isElement() const; - virtual bool isText() const; - virtual bool isCDATASection() const; - virtual bool isDocument() const; - virtual bool isDocumentType() const; - - virtual void clear(); - KoXmlElement toElement() const; - KoXmlText toText() const; - KoXmlCDATASection toCDATASection() const; - KoXmlDocument toDocument() const; - - virtual QString nodeName() const; - virtual QString namespaceURI() const; - virtual QString prefix() const; - virtual QString localName() const; - - KoXmlDocument ownerDocument() const; - KoXmlNode parentNode() const; - - bool hasChildNodes() const; - KoXmlNode firstChild() const; - KoXmlNode lastChild() const; - KoXmlNode nextSibling() const; - KoXmlNode previousSibling() const; - - KoXmlElement firstChildElement() const; - - // equivalent to node.childNodes().count() if node is a QDomNode instance - int childNodesCount() const; - - // workaround to get and iterate over all attributes - QStringList attributeNames() const; - QList< QPair > attributeFullNames() const; - - KoXmlNode namedItem(const QString& name) const; - KoXmlNode namedItemNS(const QString& nsURI, const QString& name) const; - KoXmlNode namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const; - - /** - * Loads all child nodes (if any) of this node. Normally you do not need - * to call this function as the child nodes will be automatically - * loaded when necessary. - */ - void load(int depth = 1); - - /** - * Releases all child nodes of this node. - */ - void unload(); - - // compatibility - /** - * @internal do not call directly - * Use KoXml::asQDomDocument(), KoXml::asQDomElement() or KoXml::asQDomNode() instead - */ - void asQDomNode(QDomDocument& ownerDoc) const; - -protected: - KoXmlNodeData* d; - explicit KoXmlNode(KoXmlNodeData*); -}; - -/** -* KoXmlElement represents a tag element in a DOM tree. -* -* KoXmlElement holds information about an XML tag, along with its attributes. -* -* @author Ariya Hidayat -*/ - -class KRITASTORE_EXPORT KoXmlElement: public KoXmlNode -{ -public: - KoXmlElement(); - KoXmlElement(const KoXmlElement& element); - KoXmlElement& operator=(const KoXmlElement& element); - ~KoXmlElement() override; - bool operator== (const KoXmlElement&) const; - bool operator!= (const KoXmlElement&) const; - - QString tagName() const; - QString text() const; - - QString attribute(const QString& name) const; - QString attribute(const QString& name, const QString& defaultValue) const; - QString attributeNS(const QString& namespaceURI, const QString& localName, - const QString& defaultValue = QString()) const; - bool hasAttribute(const QString& name) const; - bool hasAttributeNS(const QString& namespaceURI, const QString& localName) const; - -private: - friend class KoXmlNode; - friend class KoXmlDocument; - explicit KoXmlElement(KoXmlNodeData*); -}; - -/** -* KoXmlText represents a text in a DOM tree. -* @author Ariya Hidayat -*/ -class KRITASTORE_EXPORT KoXmlText: public KoXmlNode -{ -public: - KoXmlText(); - KoXmlText(const KoXmlText& text); - KoXmlText& operator=(const KoXmlText& text); - ~KoXmlText() override; - - QString data() const; - bool isText() const override; - -private: - friend class KoXmlNode; - friend class KoXmlCDATASection; - friend class KoXmlDocument; - explicit KoXmlText(KoXmlNodeData*); -}; - -/** -* KoXmlCDATASection represents a CDATA section in a DOM tree. -* @author Ariya Hidayat -*/ -class KRITASTORE_EXPORT KoXmlCDATASection: public KoXmlText -{ -public: - KoXmlCDATASection(); - KoXmlCDATASection(const KoXmlCDATASection& cdata); - KoXmlCDATASection& operator=(const KoXmlCDATASection& cdata); - ~KoXmlCDATASection() override; - - bool isCDATASection() const override; - -private: - friend class KoXmlNode; - friend class KoXmlDocument; - explicit KoXmlCDATASection(KoXmlNodeData*); -}; - -/** -* KoXmlDocumentType represents the DTD of the document. At the moment, -* it can used only to get the document type, i.e. no support for -* entities etc. -* -* @author Ariya Hidayat -*/ - -class KRITASTORE_EXPORT KoXmlDocumentType: public KoXmlNode -{ -public: - KoXmlDocumentType(); - KoXmlDocumentType(const KoXmlDocumentType&); - KoXmlDocumentType& operator=(const KoXmlDocumentType&); - ~KoXmlDocumentType() override; - - QString name() const; - -private: - friend class KoXmlNode; - friend class KoXmlDocument; - friend class KoXmlDocumentData; - explicit KoXmlDocumentType(KoXmlNodeData*); -}; - - -/** -* KoXmlDocument represents an XML document, structured in a DOM tree. -* -* KoXmlDocument is designed to be memory efficient. Unlike QDomDocument from -* Qt's XML module, KoXmlDocument does not store all nodes in the DOM tree. -* Some nodes will be loaded and parsed on-demand only. -* -* KoXmlDocument is read-only, you can not modify its content. -* -* @author Ariya Hidayat -*/ - -class KRITASTORE_EXPORT KoXmlDocument: public KoXmlNode -{ -public: - explicit KoXmlDocument(bool stripSpaces = false); - KoXmlDocument(const KoXmlDocument& node); - KoXmlDocument& operator=(const KoXmlDocument& node); - bool operator==(const KoXmlDocument&) const; - bool operator!=(const KoXmlDocument&) const; - ~KoXmlDocument() override; - - KoXmlElement documentElement() const; - - KoXmlDocumentType doctype() const; - - QString nodeName() const override; - void clear() override; - - bool setContent(QIODevice* device, bool namespaceProcessing, - QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); - bool setContent(QIODevice* device, - QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); - bool setContent(QXmlStreamReader *reader, - QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); - bool setContent(const QByteArray& text, bool namespaceProcessing, - QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); - bool setContent(const QString& text, bool namespaceProcessing, - QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); - - // no namespace processing - bool setContent(const QString& text, - QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); - /** - * Change the way an XMLDocument will be read: - * if stripSpaces = true then a will only have one child - * if stripSpaces = false then a will have 3 children. - */ - void setWhitespaceStripping(bool stripSpaces); - -private: - friend class KoXmlNode; - explicit KoXmlDocument(KoXmlDocumentData*); -}; - -#endif // KOXML_USE_QDOM - /** * This namespace contains a few convenience functions to simplify code using QDom * (when loading OASIS documents, in particular). * * To find the child element with a given name, use KoXml::namedItemNS. * * To find all child elements with a given name, use * QDomElement e; * forEachElement( e, parent ) * { * if ( e.localName() == "..." && e.namespaceURI() == KoXmlNS::... ) * { * ... * } * } * Note that this means you don't ever need to use QDomNode nor toElement anymore! * Also note that localName is the part without the prefix, this is the whole point * of namespace-aware methods. * * To find the attribute with a given name, use QDomElement::attributeNS. * * Do not use getElementsByTagNameNS, it's recursive (which is never needed in Calligra). * Do not use tagName() or nodeName() or prefix(), since the prefix isn't fixed. * * @author David Faure */ namespace KoXml { /** * A namespace-aware version of QDomNode::namedItem(), * which also takes care of casting to a QDomElement. * * Use this when a domelement is known to have only *one* child element * with a given tagname. * * Note: do *NOT* use getElementsByTagNameNS, it's recursive! */ KRITASTORE_EXPORT KoXmlElement namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName); /** * A namespace-aware version of QDomNode::namedItem(). * which also takes care of casting to a QDomElement. * * Use this when you like to return the first or an invalid * KoXmlElement with a known type. * * This is an optimized version of the namedItemNS above to * give fast access to certain sections of the document using * the office-text-content-prelude condition as @a KoXmlNamedItemType . */ KRITASTORE_EXPORT KoXmlElement namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName, KoXmlNamedItemType type); /** * Explicitly load child nodes of specified node, up to given depth. * This function has no effect if QDom is used. */ KRITASTORE_EXPORT void load(KoXmlNode& node, int depth = 1); /** * Unload child nodes of specified node. * This function has no effect if QDom is used. */ KRITASTORE_EXPORT void unload(KoXmlNode& node); /** * Get the number of child nodes of specified node. */ KRITASTORE_EXPORT int childNodesCount(const KoXmlNode& node); /** * Return the name of all attributes of specified node. */ KRITASTORE_EXPORT QStringList attributeNames(const KoXmlNode& node); /** * Convert KoXmlNode classes to the corresponding QDom classes, which has * @p ownerDoc as the owner document (QDomDocument instance). * The converted @p node (and its children) are added to ownerDoc. * * NOTE: * - If ownerDoc is not empty, this may fail, @see QDomDocument * - @p node must not be a KoXmlDocument, use asQDomDocument() - * + * * @see asQDomDocument, asQDomElement */ KRITASTORE_EXPORT void asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node); /** * Convert KoXmlNode classes to the corresponding QDom classes, which has * @p ownerDoc as the owner document (QDomDocument instance). * The converted @p element (and its children) is added to ownerDoc. - * + * * NOTE: If ownerDoc is not empty, this may fail, @see QDomDocument * */ KRITASTORE_EXPORT void asQDomElement(QDomDocument& ownerDoc, const KoXmlElement& element); /** * Converts the whole @p document into a QDomDocument - * If KOXML_USE_QDOM is defined, just returns @p document */ KRITASTORE_EXPORT QDomDocument asQDomDocument(const KoXmlDocument& document); /* * Load an XML document from specified device to a document. You can of * course use it with QFile (which inherits QIODevice). * This is much more memory efficient than standard QDomDocument::setContent * because the data from the device is buffered, unlike * QDomDocument::setContent which just loads everything in memory. * * Note: it is assumed that the XML uses UTF-8 encoding. */ KRITASTORE_EXPORT bool setDocument(KoXmlDocument& doc, QIODevice* device, bool namespaceProcessing, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); } /** * \def forEachElement( elem, parent ) * \brief Loop through all child elements of \parent. * This convenience macro is used to implement the forEachElement loop. * The \elem parameter is a name of a QDomElement variable and the \parent * is the name of the parent element. For example: * * QDomElement e; * forEachElement( e, parent ) * { * qDebug() << e.localName() << " element found."; * ... * } */ #define forEachElement( elem, parent ) \ for ( KoXmlNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \ if ( ( elem = _node.toElement() ).isNull() ) {} else #endif // KO_XMLREADER_H diff --git a/libs/store/KoXmlReaderForward.h b/libs/store/KoXmlReaderForward.h index f49a7c1cb9..a615d28cd0 100644 --- a/libs/store/KoXmlReaderForward.h +++ b/libs/store/KoXmlReaderForward.h @@ -1,49 +1,37 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat Copyright (C) 2007 Thorsten Zachmann 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 KOXMLREADERFORWARD_H #define KOXMLREADERFORWARD_H // use standard QDom, useful to test KoXml classes against Qt's QDom -//#define KOXML_USE_QDOM - -#ifdef KOXML_USE_QDOM +#define KOXML_USE_QDOM #include typedef QDomNode KoXmlNode; typedef QDomElement KoXmlElement; typedef QDomText KoXmlText; typedef QDomCDATASection KoXmlCDATASection; typedef QDomDocumentType KoXmlDocumentType; typedef QDomDocument KoXmlDocument; -#else - -class KoXmlElement; -class KoXmlNode; -class KoXmlText; -class KoXmlCDATASection; -class KoXmlDocumentType; -class KoXmlDocument; - -#endif #endif // KOXMLREADERFORWARD_H diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 7c27a872f0..9bb7be39f2 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,554 +1,555 @@ 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) 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_send_telemetry.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_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 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/strokes/freehand_stroke.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_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/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_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.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 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 ) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAnimationCacheRegenerator.cpp dialogs/KisAnimationCacheUpdateProgressDialog.cpp canvas/kis_animation_player.cpp kis_animation_exporter.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() 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/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgtelemetry.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 kritalibtelemetry 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}) 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/KisApplication.cpp b/libs/ui/KisApplication.cpp index 90c594db24..4ba0aa38ec 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,769 +1,777 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2009 Thomas Zander Copyright (C) 2012 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 "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #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 "KoGlobal.h" #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplicationPrivate { public: KisApplicationPrivate() : splashScreen(0) {} QPointer splashScreen; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false); if (m_fileCount > 0 || hideSplash) { m_splash->hide(); } else { m_splash->setWindowFlags(Qt::Dialog); QRect r(QPoint(), m_splash->size()); m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center()); m_splash->setWindowTitle(qAppName()); m_splash->setParent(0); Q_FOREACH (QObject *o, m_splash->children()) { QWidget *w = qobject_cast(o); if (w && w->isHidden()) { w->setVisible(true); } } m_splash->show(); m_splash->activateWindow(); } } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new KisApplicationPrivate) , m_autosaveDialog(0) , m_mainWindow(0) , m_batchRun(false) { QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); + KoResourcePaths::addResourceType("symbols", "data", "/symbols"); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); + d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); + d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); } void KisApplication::loadResources() { setSplashScreenLoadingText(i18n("Loading Gradients...")); processEvents(); KoResourceServerProvider::instance()->gradientServer(true); // Load base resources setSplashScreenLoadingText(i18n("Loading Patterns...")); processEvents(); KoResourceServerProvider::instance()->patternServer(true); setSplashScreenLoadingText(i18n("Loading Palettes...")); processEvents(); KoResourceServerProvider::instance()->paletteServer(false); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(true); // load paintop presets setSplashScreenLoadingText(i18n("Loading Paint Operations...")); processEvents(); KisResourceServerProvider::instance()->paintOpPresetServer(true); + // load symbols + setSplashScreenLoadingText(i18n("Loading SVG Symbol Collections...")); + processEvents(); + KoResourceServerProvider::instance()->svgSymbolCollectionServer(true); + setSplashScreenLoadingText(i18n("Loading Resource Bundles...")); processEvents(); KisResourceServerProvider::instance()->resourceBundleServer(); } void KisApplication::loadPlugins() { KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg; #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doTemplate = args.doTemplate(); const bool print = args.print(); const bool exportAs = args.exportAs(); const bool exportAsPdf = args.exportAsPdf(); const QString exportFileName = args.exportFileName(); m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty()); // print & exportAsPdf do user interaction ATM const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed // TODO: fix print & exportAsPdf to work without mainwindow shown const bool showmainWindow = !exportAs; // would be !batchRun; const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH");// && qgetenv("XDG_CURRENT_DESKTOP") != "GNOME"; if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); // Initialize all Krita directories etc. KoGlobal::initialize(); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load all resources and tags before the plugins do that loadResources(); // Load the plugins loadPlugins(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); m_mainWindow = KisPart::instance()->createMainWindow(); if (showmainWindow) { m_mainWindow->initializeGeometry(); m_mainWindow->show(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf) if (!m_batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments short int nPrinted = 0; for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (m_batchRun) { continue; } if (createNewDocFromTemplate(fileName, m_mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); doc->setOutputMimeType(outputMimetype.toLatin1()); if (!doc->exportDocument(QUrl::fromLocalFile(exportFileName))) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } nPrinted++; QTimer::singleShot(0, this, SLOT(quit())); } else if (m_mainWindow) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); if (m_mainWindow->openDocumentInternal(QUrl::fromLocalFile(fileName), doc)) { if (print) { m_mainWindow->slotFilePrint(); nPrinted++; // TODO: trigger closing of app once printing is done } else if (exportAsPdf) { KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName); if (job) connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow, SLOT(slotFileQuit()), Qt::QueuedConnection); nPrinted++; } else { // Normal case, success numberOfOpenDocuments++; } } else { // .... if failed // delete doc; done by openDocument } } } } if (m_batchRun) { return nPrinted > 0; } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(); d->splashScreen->displayRecentFiles(); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { delete d; } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); mw->openDocumentInternal(QUrl::fromLocalFile(filename), doc); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); mainWindow->openDocumentInternal(QUrl::fromLocalFile(url), doc); } } void KisApplication::checkAutosaveFiles() { if (m_batchRun) return; // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! QStringList filters; filters << QString(".krita-*-*-autosave.kra"); #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // all autosave files for our application m_autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (m_autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } m_autosaveDialog = new KisAutoSaveRecoveryDialog(m_autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) m_autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = m_autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, m_autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } m_autosaveFiles = filesToRecover; } else { m_autosaveFiles.clear(); } if (m_autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, m_autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (m_mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); m_mainWindow->openDocumentInternal(url, doc); } } } // cleanup delete m_autosaveDialog; m_autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); if (mainWindow->openDocumentInternal(templateURL, doc)) { doc->resetURL(); doc->setTitleModified(); dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 320e875368..5b33c20891 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1768 +1,1773 @@ /* 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 // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include #include #include "kis_telemetry_instance.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; /********************************************************** * * KisDocument * **********************************************************/ namespace { class DocumentProgressProxy : public KoProgressProxy { public: KisMainWindow *m_mainWindow; DocumentProgressProxy(KisMainWindow *mainWindow) : m_mainWindow(mainWindow) { } ~DocumentProgressProxy() override { // signal that the job is done setValue(-1); } int maximum() const override { return 100; } void setValue(int value) override { if (m_mainWindow) { m_mainWindow->slotProgress(value); } } void setRange(int /*minimum*/, int /*maximum*/) override { } void setFormat(const QString &/*format*/) override { } }; } //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) : 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() : docInfo(0), progressUpdater(0), progressProxy(0), importExportManager(0), isImporting(false), isExporting(false), password(QString()), modifiedAfterAutosave(false), isAutosaving(false), backupFile(true), doNotSaveExtDoc(false), undoStack(0), m_saveOk(false), m_waitForSave(false), m_duringSaveAs(false), m_bAutoDetectedMime(false), modified(false), readwrite(true), disregardAutosaveFailure(false), nserver(0), macroNestDepth(0), imageIdleWatcher(2000 /*ms*/), suppressProgress(false), fileProgressProxy(0), savingLock(&savingMutex) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo; KoProgressUpdater *progressUpdater; KoProgressProxy *progressProxy; KoUnit unit; KisImportExportManager *importExportManager; // 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 bool isImporting; bool isExporting; // File --> Import/Export vs File --> Open/Save QString password; // The password used to encrypt an encrypted document QTimer autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay {300}; // in seconds, 0 to disable. bool modifiedAfterAutosave; bool isAutosaving; bool backupFile; bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents KUndo2Stack *undoStack; KisGuidesConfig guidesConfig; QUrl m_originalURL; // for saveAs QString m_originalFilePath; // for saveAs bool m_saveOk; bool m_waitForSave; bool m_duringSaveAs; bool m_bAutoDetectedMime; // 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. QEventLoop m_eventLoop; QMutex savingMutex; bool modified; bool readwrite; QDateTime firstMod; QDateTime lastMod; bool disregardAutosaveFailure; KisNameServer *nserver; qint32 macroNestDepth; KisImageSP image; KisImageSP savingImage; KisNodeSP preActivatedNode; KisShapeController* shapeController; KoShapeController* koShapeController; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; bool suppressProgress; KoProgressProxy* fileProgressProxy; QList assistants; KisGridConfig gridConfig; StdLockableWrapper savingLock; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class SafeSavingLocker; }; class KisDocument::Private::SafeSavingLocker { public: SafeSavingLocker(KisDocument::Private *_d, KisDocument *document) : d(_d) , m_document(document) , m_locked(false) , m_imageLock(d->image, true) { const int realAutoSaveInterval = KisConfig().autoSaveInterval(); const int emergencyAutoSaveInterval = 10; // sec /** * 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, d->savingLock) < 0; if (!m_locked) { if (d->isAutosaving) { d->disregardAutosaveFailure = true; if (realAutoSaveInterval) { m_document->setAutoSaveDelay(emergencyAutoSaveInterval); } } else { d->image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, d->savingLock) < 0; } } if (m_locked) { d->disregardAutosaveFailure = false; } } ~SafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); d->savingLock.unlock(); const int realAutoSaveInterval = KisConfig().autoSaveInterval(); m_document->setAutoSaveDelay(realAutoSaveInterval); } } bool successfullyLocked() const { return m_locked; } private: KisDocument::Private *d; KisDocument *m_document; bool m_locked; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private()) { d->undoStack = new UndoStack(this); d->undoStack->setParent(this); d->importExportManager = new KisImportExportManager(this); d->importExportManager->setProgresUpdater(d->progressUpdater); connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); KisConfig cfg; setAutoSaveDelay(cfg.autoSaveInterval()); setObjectName(newObjectName()); d->docInfo = new KoDocumentInfo(this); d->firstMod = QDateTime::currentDateTime(); d->lastMod = QDateTime::currentDateTime(); // preload the krita resources KisResourceServerProvider::instance(); d->nserver = new KisNameServer(1); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); undoStack()->setUndoLimit(KisConfig().undoStackLimit()); connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int))); setBackupFile(KisConfig().backupFile()); } KisDocument::~KisDocument() { /** * 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; } bool KisDocument::exportDocument(const QUrl &_url, KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "exportDocument" << _url.toDisplayString() << "is autosaving" << d->isAutosaving; bool ret; d->isExporting = true; // // Preserve a lot of state here because we need to restore it in order to // be able to fake a File --> Export. Can't do this in saveFile() because, // for a start, KParts has already set url and m_file and because we need // to restore the modified flag etc. and don't want to put a load on anyone // reimplementing saveFile() (Note: importDocument() and exportDocument() // will remain non-virtual). // QUrl oldURL = url(); QString oldFile = localFilePath(); //qDebug() << "\toldUrl" << oldURL << "oldFile" << oldFile << "export url" << _url; bool wasModified = isModified(); // save... ret = saveAs(_url, exportConfiguration); // // This is sooooo hacky :( // Hopefully we will restore enough state. // dbgUI << "Restoring KisDocument state to before export"; // always restore url & m_file regardless of failure or success //qDebug() << "\tafter saveAs: url" << url() << "local file path" << localFilePath(); setUrl(oldURL); setLocalFilePath(oldFile); //qDebug() << "\tafter restoring: url" << url() << "local file path" << localFilePath(); // on successful export we need to restore modified etc. too // on failed export, mimetype/modified hasn't changed anyway if (ret) { setModified(wasModified); } d->isExporting = false; return ret; } bool KisDocument::saveAs(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "saveAs" << url; if (!url.isValid() || !url.isLocalFile()) { errKrita << "saveAs: Malformed URL " << url.url() << endl; return false; } d->m_duringSaveAs = true; d->m_originalURL = d->m_url; d->m_originalFilePath = d->m_file; d->m_url = url; // Store where to upload in saveToURL d->m_file = d->m_url.toLocalFile(); bool result = save(exportConfiguration); // Save local file and upload local file if (!result) { d->m_url = d->m_originalURL; d->m_file = d->m_originalFilePath; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); } return result; } bool KisDocument::save(KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "save" << d->m_file << d->m_url << url() << localFilePath(); d->m_saveOk = false; if (d->m_file.isEmpty()) { // document was created empty d->m_file = d->m_url.toLocalFile(); } updateEditingTime(true); setFileProgressProxy(); setUrl(url()); bool ok = saveFile(localFilePath(), exportConfiguration); clearFileProgressProxy(); if (ok) { setModified( false ); emit completed(); d->m_saveOk = true; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); return true; // Nothing to do } else { emit canceled(QString()); } return false; } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); if (!prepareLocksForSaving()) { return byteArray; } if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } unlockAfterSaving(); return byteArray; } bool KisDocument::isInSaving() const { std::unique_lock> l(d->savingLock, std::try_to_lock); return !l.owns_lock(); } bool KisDocument::saveFile(const QString &filePath, KisPropertiesConfigurationSP exportConfiguration) { if (!prepareLocksForSaving()) { return false; } // Unset the error message setErrorMessage(""); // Save it to be able to restore it after a failed save const bool wasModified = isModified(); bool ret = false; bool suppressErrorDialog = fileBatchMode(); KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; //qDebug() << "saveFile" << localFilePath() << QFileInfo(localFilePath()).exists() << !QFileInfo(localFilePath()).isWritable(); if (QFileInfo(localFilePath()).exists() && !QFileInfo(localFilePath()).isWritable()) { setErrorMessage(i18n("%1 cannot be written to. Please save under a different name.", localFilePath())); } else { // The output format is set by KisMainWindow, and by openFile QByteArray outputMimeType = d->outputMimeType; if (outputMimeType.isEmpty()) { outputMimeType = d->outputMimeType = nativeFormatMimeType(); } //qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType; if (d->backupFile) { Q_ASSERT(url().isLocalFile()); KBackup::backupFile(url().toLocalFile()); } qApp->processEvents(); setFileProgressUpdater(i18n("Saving Document")); //qDebug() << "saving to tempory file" << tempororaryFileName; status = d->importExportManager->exportDocument(localFilePath(), filePath, outputMimeType, !d->isExporting , exportConfiguration); ret = (status == KisImportExportFilter::OK); suppressErrorDialog = (fileBatchMode() || isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph); //qDebug() << "Export status was" << status; if (ret) { if (!d->isAutosaving && !d->suppressProgress) { QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack"); updater->setProgress(0); d->undoStack->setClean(); updater->setProgress(100); } else { d->undoStack->setClean(); } if (errorMessage().isEmpty()) { if (!isAutosaving()) { removeAutoSaveFiles(); } } else { ret = false; qWarning() << "Error while saving:" << errorMessage(); } // Restart the autosave timer // (we don't want to autosave again 2 seconds after a real save) if (!isAutosaving()) { setAutoSaveDelay(d->autoSaveDelay); } d->mimeType = outputMimeType; KisSaveImageProperties kisSaveImageProperties(d->image); QString fileName = localFilePath(); kisSaveImageProperties.doAction(KisTelemetryInstance::instance()->provider(), fileName); } } if (!ret) { if (!suppressErrorDialog) { if (errorMessage().isEmpty()) { setErrorMessage(KisImportExportFilter::conversionStatusString(status)); } if (errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", filePath)); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage())); } } // couldn't save file so this new URL is invalid // FIXME: we should restore the current document's true URL instead of // setting it to nothing otherwise anything that depends on the URL // being correct will not work (i.e. the document will be called // "Untitled" which may not be true) // // Update: now the URL is restored in KisMainWindow but really, this // should still be fixed in KisDocument/KParts (ditto for file). // We still resetURL() here since we may or may not have been called // by KisMainWindow - Clarence resetURL(); // As we did not save, restore the "was modified" status setModified(wasModified); } emit sigSavingFinished(); clearFileProgressUpdater(); unlockAfterSaving(); return ret; } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } void KisDocument::setOutputMimeType(const QByteArray & mimeType) { d->outputMimeType = mimeType; } QByteArray KisDocument::outputMimeType() const { return d->outputMimeType; } bool KisDocument::fileBatchMode() const { return d->importExportManager->batchMode(); } void KisDocument::setFileBatchMode(const bool batchMode) { d->importExportManager->setBatchMode(batchMode); } bool KisDocument::isImporting() const { return d->isImporting; } bool KisDocument::isExporting() const { return d->isExporting; } void KisDocument::slotAutoSave() { //qDebug() << "slotAutoSave. Modified:" << d->modified << "modifiedAfterAutosave" << d->modified << "url" << url() << localFilePath(); if (!d->isAutosaving && d->modified && d->modifiedAfterAutosave) { + bool batchmode = d->importExportManager->batchMode(); + d->importExportManager->setBatchMode(true); + qApp->setOverrideCursor(Qt::BusyCursor); connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); emit statusBarMessage(i18n("Autosaving...")); d->isAutosaving = true; QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); QByteArray mimetype = d->outputMimeType; d->outputMimeType = nativeFormatMimeType(); bool ret = exportDocument(QUrl::fromLocalFile(autoSaveFileName)); d->outputMimeType = mimetype; if (ret) { d->modifiedAfterAutosave = false; d->autoSaveTimer.stop(); // until the next change } + qApp->restoreOverrideCursor(); + d->importExportManager->setBatchMode(batchmode); d->isAutosaving = false; emit clearStatusBarMessage(); disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); if (!ret && !d->disregardAutosaveFailure) { emit statusBarMessage(i18n("Error during autosave! Partition full?")); } } } 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"); if (path.isEmpty()) { // 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 { QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); 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(); d->isImporting = true; // 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(); } d->isImporting = false; return ret; } bool KisDocument::openUrl(const QUrl &_url, KisDocument::OpenUrlFlags 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) { resetURL(); // Force save to act like 'Save As' setReadWrite(true); // enable save button setModified(true); } else { if( !(flags & OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES) ) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("dialog-warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-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; setFileProgressUpdater(i18n("Opening Document")); 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(); } clearFileProgressUpdater(); 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(); if (!d->suppressProgress && d->progressUpdater) { QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack"); updater->setProgress(0); undoStack()->clear(); updater->setProgress(100); clearFileProgressUpdater(); } else { undoStack()->clear(); } return true; } KoProgressUpdater *KisDocument::progressUpdater() const { return d->progressUpdater; } void KisDocument::setProgressProxy(KoProgressProxy *progressProxy) { d->progressProxy = progressProxy; } KoProgressProxy* KisDocument::progressProxy() const { if (!d->progressProxy) { KisMainWindow *mainWindow = 0; if (KisPart::instance()->mainwindowCount() > 0) { mainWindow = KisPart::instance()->mainWindows()[0]; } d->progressProxy = new DocumentProgressProxy(mainWindow); } return d->progressProxy; } // 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; if (documentInfo()) { c = documentInfo()->aboutInfo("title"); } const QString _url(url().fileName()); if (!c.isEmpty() && !_url.isEmpty()) { c = QString("%1 - %2").arg(c).arg(_url); } else if (c.isEmpty()) { 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); } } void KisDocument::setBackupFile(bool saveBackup) { d->backupFile = saveBackup; } 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::slotUndoStackIndexChanged(int idx) { // even if the document was already modified, call setModified to re-start autosave timer setModified(idx != d->undoStack->cleanIndex()); } 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; } setFileProgressProxy(); setUrl(d->m_url); ret = openFile(); clearFileProgressProxy(); 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(QDesktopServices::storageLocation(QDesktopServices::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; } KoShapeBasedDocumentBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } vKisNodeSP KisDocument::activeNodes() const { vKisNodeSP nodes; Q_FOREACH (KisView *v, KisPart::instance()->views()) { if (v->document() == this && v->viewManager()) { KisNodeSP activeNode = v->viewManager()->activeNode(); if (activeNode && !nodes.contains(activeNode)) { if (activeNode->inherits("KisMask")) { activeNode = activeNode->parent(); } nodes.append(activeNode); } } } return nodes; } 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; } void KisDocument::setFileProgressUpdater(const QString &text) { d->suppressProgress = d->importExportManager->batchMode(); if (!d->suppressProgress) { d->progressUpdater = new KoProgressUpdater(d->progressProxy, KoProgressUpdater::Unthreaded); d->progressUpdater->start(100, text); d->importExportManager->setProgresUpdater(d->progressUpdater); if (KisPart::instance()->currentMainwindow()) { connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); connect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled())); } } } void KisDocument::clearFileProgressUpdater() { if (!d->suppressProgress && d->progressUpdater) { if (KisPart::instance()->currentMainwindow()) { disconnect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled())); disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); } delete d->progressUpdater; d->importExportManager->setProgresUpdater(0); d->progressUpdater = 0; } } void KisDocument::setFileProgressProxy() { if (!d->progressProxy && !d->importExportManager->batchMode()) { d->fileProgressProxy = progressProxy(); } else { d->fileProgressProxy = 0; } } void KisDocument::clearFileProgressProxy() { if (d->fileProgressProxy) { setProgressProxy(0); delete d->fileProgressProxy; d->fileProgressProxy = 0; } } 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; } bool KisDocument::prepareLocksForSaving() { KisImageSP copiedImage; // XXX: Restore this when // a) cloning works correctly and // b) doesn't take ages because it needs to refresh its entire graph and finally, // c) we do use the saving image to save in the background. { Private::SafeSavingLocker locker(d, this); if (locker.successfullyLocked()) { copiedImage = d->image; //->clone(true); } else if (!isAutosaving()) { // even though it is a recovery operation, we should ensure we do not enter saving twice! std::unique_lock> l(d->savingLock, std::try_to_lock); if (l.owns_lock()) { d->lastErrorMessage = i18n("The image was still busy while saving. Your saved image might be incomplete."); d->image->lock(); copiedImage = d->image; //->clone(true); //copiedImage->initialRefreshGraph(); d->image->unlock(); } } } bool result = false; // ensure we do not enter saving twice if (copiedImage && d->savingMutex.tryLock()) { d->savingImage = copiedImage; result = true; } else { qWarning() << "Could not lock the document for saving!"; d->lastErrorMessage = i18n("Could not lock the image for saving."); } return result; } void KisDocument::unlockAfterSaving() { d->savingImage = 0; d->savingMutex.unlock(); } diff --git a/libs/ui/dialogs/kis_dlg_png_import.cpp b/libs/ui/dialogs/kis_dlg_png_import.cpp index 0b9c5de286..b976b60090 100644 --- a/libs/ui/dialogs/kis_dlg_png_import.cpp +++ b/libs/ui/dialogs/kis_dlg_png_import.cpp @@ -1,67 +1,64 @@ /* * Copyright (C) 2015 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_dlg_png_import.h" #include #include #include #include #include #include #include "widgets/squeezedcombobox.h" #include "kis_config.h" KisDlgPngImport::KisDlgPngImport(const QString &path, const QString &colorModelID, const QString &colorDepthID, QWidget *parent) : KoDialog(parent) { setButtons(Ok); setDefaultButton(Ok); QWidget *page = new QWidget(this); dlgWidget.setupUi(page); setMainWidget(page); dlgWidget.lblFilename->setText(path); - QString s = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID); + const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID); dlgWidget.cmbProfile->clear(); - const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s); - if (csf) { - QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf); - QStringList profileNames; - Q_FOREACH (const KoColorProfile *profile, profileList) { - profileNames.append(profile->name()); - } - qSort(profileNames); - Q_FOREACH (QString stringName, profileNames) { - dlgWidget.cmbProfile->addSqueezedItem(stringName); - } - KisConfig cfg; - QString profile = cfg.readEntry("pngImportProfile", csf->defaultProfile()); - dlgWidget.cmbProfile->setCurrent(profile); + QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); + QStringList profileNames; + Q_FOREACH (const KoColorProfile *profile, profileList) { + profileNames.append(profile->name()); } + qSort(profileNames); + Q_FOREACH (QString stringName, profileNames) { + dlgWidget.cmbProfile->addSqueezedItem(stringName); + } + KisConfig cfg; + QString profile = cfg.readEntry("pngImportProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId)); + dlgWidget.cmbProfile->setCurrent(profile); } QString KisDlgPngImport::profile() const { QString p = dlgWidget.cmbProfile->itemHighlighted(); KisConfig cfg; cfg.writeEntry("pngImportProfile", p); return p; } diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 4b72ddc9d1..5cac7e5ea1 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1128 +1,1123 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 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_dlg_preferences.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 "KoID.h" #include #include #include #include #include #include "kis_action_registry.h" #include "widgets/squeezedcombobox.h" #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorConversionTransformation.h" #include "kis_cursor.h" #include "kis_config.h" #include "kis_canvas_resource_provider.h" #include "kis_preference_set_registry.h" #include "kis_color_manager.h" #include "KisProofingConfiguration.h" #include "kis_image_config.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include "input/config/kis_input_configuration_page.h" GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg; m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); chkShowRootLayer->setChecked(cfg.showRootLayer()); int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); m_undoStackSize->setValue(cfg.undoStackLimit()); m_backupFileCheckBox->setChecked(cfg.backupFile()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_hideSplashScreen->setChecked(cfg.hideSplashScreen()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", true)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor()); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); m_chkCompressKra->setChecked(cfg.compressKra()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", false).toBool()); m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); } void GeneralTab::setDefault() { KisConfig cfg; m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_hideSplashScreen->setChecked(cfg.hideSplashScreen(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground(true)); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } bool GeneralTab::hideSplashScreen() { return m_hideSplashScreen->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } bool GeneralTab::calculateAnimationCacheInBackground() { return m_chkCacheAnimatioInBackground->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; SqueezedComboBox *cmb = new SqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { if (useSystemProfile) { KisConfig cfg; QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::desktop()->screenCount()) { for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA", "")); KisConfig cfg; KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } -void ColorSettingsTab::refillMonitorProfiles(const KoID & s) +void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { - const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s.id()); - for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } - if (!csf) - return; - QMap profileList; - Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(csf)) { + Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); - m_monitorProfileWidgets[i]->setCurrent(csf->defaultProfile()); + m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { KisImageConfig cfg; return cfg.totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg; const int totalRAM = cfg.totalRAM(); lblTotalMemory->setText(i18n("%1 MiB", totalRAM)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg; sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); { KisConfig cfg2; chkOpenGLLogging->setChecked(cfg2.enableOpenGLDebugging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); } } void PerformanceTab::save() { KisImageConfig cfg; cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); { KisConfig cfg2; cfg2.setEnableOpenGLDebugging(chkOpenGLLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); } } void PerformanceTab::selectSwapDir() { KisImageConfig cfg; QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); lblSwapFileLocation->setText(swapDir); } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg; if (!KisOpenGL::hasOpenGL()) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KoColor c; c.fromQColor(cfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(cfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); } void DisplaySettingsTab::setDefault() { KisConfig cfg; if (!KisOpenGL::hasOpenGL()) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); button(QDialogButtonBox::Ok)->setDefault(true); setFaceType(KPageDialog::List); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) { KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); } KisDlgPreferences::~KisDlgPreferences() { } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg; cfg.setNewCursorStyle(dialog->m_general->cursorStyle()); cfg.setNewOutlineStyle(dialog->m_general->outlineStyle()); cfg.setShowRootLayer(dialog->m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting()); cfg.setHideSplashScreen(dialog->m_general->hideSplashScreen()); cfg.setCalculateAnimationCacheInBackground(dialog->m_general->calculateAnimationCacheInBackground()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", dialog->m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", dialog->m_general->mdiMode()); cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); KisPart *part = KisPart::instance(); if (part) { Q_FOREACH (QPointer doc, part->documents()) { if (doc) { doc->setAutoSaveDelay(dialog->m_general->autoSaveInterval()); doc->setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); doc->undoStack()->setUndoLimit(dialog->m_general->undoStackSize()); } } } cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage; cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), dialog->m_colorSettings->m_page->gamutAlarm->color(), (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); dialog->m_performanceSettings->save(); if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); cfg.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState()); dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp index 03cacef629..91205c5a58 100644 --- a/libs/ui/flake/kis_shape_controller.cpp +++ b/libs/ui/flake/kis_shape_controller.cpp @@ -1,249 +1,261 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_controller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include "kis_shape_selection.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "kis_adjustment_layer.h" #include "kis_clone_layer.h" #include "canvas/kis_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_group_layer.h" #include "kis_node_shape.h" #include "kis_node_shapes_graph.h" #include "kis_name_server.h" #include "kis_mask.h" #include "kis_shape_layer.h" #include "KisViewManager.h" #include "kis_node.h" #include #include #include #include #include "KoSelectedShapesProxy.h" struct KisShapeController::Private { public: KisDocument *doc; KisNameServer *nameServer; KisNodeShapesGraph shapesGraph; }; KisShapeController::KisShapeController(KisDocument *doc, KisNameServer *nameServer) : KisDummiesFacadeBase(doc) , m_d(new Private()) { m_d->doc = doc; m_d->nameServer = nameServer; resourceManager()->setUndoStack(doc->undoStack()); } KisShapeController::~KisShapeController() { KisNodeDummy *node = m_d->shapesGraph.rootDummy(); if (node) { m_d->shapesGraph.removeNode(node->node()); } delete m_d; } void KisShapeController::addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) { KisNodeShape *newShape = m_d->shapesGraph.addNode(node, parent, aboveThis); // XXX: what are we going to do with this shape? Q_UNUSED(newShape); KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { /** * Forward signals for global shape manager * \see comment in the constructor of KisCanvas2 */ connect(shapeLayer, SIGNAL(selectionChanged()), SIGNAL(selectionChanged())); connect(shapeLayer->shapeManager(), SIGNAL(selectionContentChanged()), SIGNAL(selectionContentChanged())); connect(shapeLayer, SIGNAL(currentLayerChanged(const KoShapeLayer*)), SIGNAL(currentLayerChanged(const KoShapeLayer*))); } } void KisShapeController::removeNodeImpl(KisNodeSP node) { KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { shapeLayer->disconnect(this); } m_d->shapesGraph.removeNode(node); } bool KisShapeController::hasDummyForNode(KisNodeSP node) const { return m_d->shapesGraph.containsNode(node); } KisNodeDummy* KisShapeController::dummyForNode(KisNodeSP node) const { return m_d->shapesGraph.nodeToDummy(node); } KisNodeDummy* KisShapeController::rootDummy() const { return m_d->shapesGraph.rootDummy(); } int KisShapeController::dummiesCount() const { return m_d->shapesGraph.shapesCount(); } static inline bool belongsToShapeSelection(KoShape* shape) { return dynamic_cast(shape->userData()); } void KisShapeController::addShapes(const QList shapes) { KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty()); KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); const KoShape *baseShapeParent = shapes.first()->parent(); const bool baseBelongsToSelection = belongsToShapeSelection(shapes.first()); bool allSameParent = true; bool allSameBelongsToShapeSelection = true; bool hasNullParent = false; Q_FOREACH (KoShape *shape, shapes) { hasNullParent |= !shape->parent(); allSameParent &= shape->parent() == baseShapeParent; allSameBelongsToShapeSelection &= belongsToShapeSelection(shape) == baseBelongsToSelection; } KIS_SAFE_ASSERT_RECOVER_RETURN(!baseBelongsToSelection || allSameBelongsToShapeSelection); if (!allSameParent || hasNullParent) { if (baseBelongsToSelection && allSameBelongsToShapeSelection) { KisSelectionSP selection = canvas->viewManager()->selection(); if (selection) { if (!selection->shapeSelection()) { selection->setShapeSelection(new KisShapeSelection(image(), selection)); } KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); Q_FOREACH(KoShape *shape, shapes) { shapeSelection->addShape(shape); } } } else { KisShapeLayer *shapeLayer = dynamic_cast( canvas->selectedShapesProxy()->selection()->activeLayer()); if (!shapeLayer) { shapeLayer = new KisShapeLayer(this, image(), i18n("Vector Layer %1", m_d->nameServer->number()), OPACITY_OPAQUE_U8); image()->undoAdapter()->addCommand(new KisImageLayerAddCommand(image(), shapeLayer, image()->rootLayer(), image()->rootLayer()->childCount())); } + QRectF updateRect; + Q_FOREACH(KoShape *shape, shapes) { shapeLayer->addShape(shape); + updateRect |= shape->boundingRect(); } + + canvas->shapeManager()->update(updateRect); } } m_d->doc->setModified(true); } void KisShapeController::removeShape(KoShape* shape) { /** * Krita layers have their own destruction path. * It goes through slotRemoveNode() */ Q_ASSERT(shape->shapeId() != KIS_NODE_SHAPE_ID && shape->shapeId() != KIS_SHAPE_LAYER_ID); + + QRectF updateRect = shape->boundingRect(); shape->setParent(0); + + KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); + KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); + canvas->shapeManager()->update(updateRect); + m_d->doc->setModified(true); } QRectF KisShapeController::documentRectInPixels() const { return m_d->doc->image()->bounds(); } qreal KisShapeController::pixelsPerInch() const { return m_d->doc->image()->xRes() * 72.0; } void KisShapeController::setInitialShapeForCanvas(KisCanvas2 *canvas) { if (!image()) return; KisNodeSP rootNode = image()->root(); if (m_d->shapesGraph.containsNode(rootNode)) { Q_ASSERT(canvas); Q_ASSERT(canvas->shapeManager()); KoSelection *selection = canvas->shapeManager()->selection(); if (selection && m_d->shapesGraph.nodeToShape(rootNode)) { selection->select(m_d->shapesGraph.nodeToShape(rootNode)); KoToolManager::instance()->switchToolRequested(KoToolManager::instance()->preferredToolForSelection(selection->selectedShapes())); } } } KoShapeLayer* KisShapeController::shapeForNode(KisNodeSP node) const { if (node) { return m_d->shapesGraph.nodeToShape(node); } return 0; } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index f882411b74..fe7cb14c3f 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,629 +1,643 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * 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_shape_layer.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 #include #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); + + /** + * The shape is always added with the absolute transformation set appropiately. + * Here we should just squeeze it into the layer's transformation. + */ + KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); + if (inheritsTransform(child)) { + QTransform parentTransform = q->absoluteTransformation(0); + child->applyAbsoluteTransformation(parentTransform.inverted()); + } } void remove(KoShape *child) override { + KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); + if (inheritsTransform(child)) { + QTransform parentTransform = q->absoluteTransformation(0); + child->applyAbsoluteTransformation(parentTransform); + } + SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvas * canvas; KoShapeBasedDocumentBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeBasedDocumentBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(controller); Q_FOREACH (KoShape *shape, _rhs.shapes()) { addShape(shape->cloneShape()); } } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { addShape(shape->cloneShape()); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { addShape(shape->cloneShape()); } } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeBasedDocumentBase* controller) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); m_d->canvas = new KisShapeLayerCanvas(this, image()); m_d->canvas->setProjection(m_d->paintDevice); m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(const QPointF&)), SLOT(slotMoveShapes(const QPointF&))); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0) } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { KisExternalLayer::setVisible(visible, isLoading); } void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } #include "SvgWriter.h" #include "SvgParser.h" #include bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes, sizeInPt); writer.save(storeDev); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { - QXmlStreamReader reader(device); - reader.setNamespaceProcessing(false); QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(device, false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { warnKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // We work fine without a master page KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { // FIXME: investigate what is this // KoShapeLayer * l = new KoShapeLayer(); if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeLayer::resetCache() { m_d->paintDevice->clear(); QList shapes = m_d->canvas->shapeManager()->shapes(); Q_FOREACH (const KoShape* shape, shapes) { shape->update(); } } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform realTransform = converter->documentToView() * transform * converter->viewToDocument(); QList oldTransformations; QList newTransformations; QList newShadows; const qreal transformBaseScale = KoUnit::approxTransformScale(transform); Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform * oldTransform); KoShapeShadow *shadow = 0; if (shape->shadow()) { shadow = new KoShapeShadow(*shape->shadow()); shadow->setOffset(transformBaseScale * shadow->offset()); shadow->setBlur(transformBaseScale * shadow->blur()); } newShadows.append(shadow); } KUndo2Command *parentCommand = new KUndo2Command(); new KoShapeTransformCommand(shapes, oldTransformations, newTransformations, parentCommand); new KoShapeShadowCommand(shapes, newShadows, parentCommand); return parentCommand; } diff --git a/libs/ui/forms/KisOpenPaneBase.ui b/libs/ui/forms/KisOpenPaneBase.ui index 1768c7e224..09275f8796 100644 --- a/libs/ui/forms/KisOpenPaneBase.ui +++ b/libs/ui/forms/KisOpenPaneBase.ui @@ -1,96 +1,91 @@ KisOpenPaneBase 0 0 505 270 0 0 - - - - - Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 170 + 0 + + + + + 48 + 48 + + + + false + + + false + + + true + + + true + + + false + + + + 1 + + + + + + + + Cancel + + + + + + + + + true + + + + 1 + 0 + - - - - - - - 0 - 0 - - - - - 170 - 0 - - - - - 48 - 48 - - - - false - - - false - - - true - - - true - - - false - - - - 1 - - - - - - - - Cancel - - - - - - - - true - - - - 1 - 0 - - - - + diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui index 9581309781..4b06a35935 100644 --- a/libs/ui/forms/wdggeneralsettings.ui +++ b/libs/ui/forms/wdggeneralsettings.ui @@ -1,690 +1,677 @@ WdgGeneralSettings 0 0 759 468 - + 0 0 552 295 Qt::LeftToRight 0 Cursor 10 10 10 10 10 10 0 0 Cursor Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Outline Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + 0 0 200 0 Show brush outline while painting - - - - Qt::Horizontal - - - - 40 - 20 - - - - Qt::Vertical 20 40 Window 0 0 Multiple Document Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 1 Subwindows Tabs Background Image (overrides color): 200 0 QFrame::StyledPanel QFrame::Sunken ... 0 0 Clear Window Background: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Don't show contents when moving sub-windows: 0 0 Show on-canvas popup messages: Qt::Vertical 20 40 Enable Hi-DPI support: Allow only one instance of Krita: Tools 10 10 10 10 10 Tool Options Location (needs restart) In Doc&ker In Tool&bar true Switch Control/Alt Selection Modifiers Qt::Vertical 20 40 Qt::Horizontal 40 20 Miscellaneous 0 0 Qt::RightToLeft Autosave every: true 0 0 75 0 min 1 1440 5 15 Compress .kra files more (slows loading/saving) Create backup file On importing images as layers, convert to the image colorspace 0 0 Undo stack size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 0 1000 5 30 0 0 Number of Palette Presets Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 10 30 Show root layer Hide splash screen on startup Warning: if you enable this setting and the file dialogs do weird stuff, do not report a bug. Enable native file dialogs (warning: may not work correctly on some systems) Maximum brush size: 0 0 The maximum diameter of a brush in pixels. px 100 10000 1000 (Needs restart) Qt::Vertical 504 13 Recalculate animation cache in background KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorButton QPushButton
kis_color_button.h
diff --git a/libs/ui/forms/wdgpaintopsettings.ui b/libs/ui/forms/wdgpaintopsettings.ui index 6b523533bc..a31e331e3a 100644 --- a/libs/ui/forms/wdgpaintopsettings.ui +++ b/libs/ui/forms/wdgpaintopsettings.ui @@ -1,569 +1,568 @@ WdgPaintOpSettings 0 0 1405 512 Brush Editor 0 1 0 4 120 0 QFrame::StyledPanel QFrame::Raised 20 5 5 5 5 0 0 Erase mode will use a separate brush size Eraser switch size false 0 0 Temporarily Save Tweaks To Presets true 0 0 Erase mode will use a separate brush opacity Eraser switch opacity false Qt::Horizontal 40 20 60 0 10 5 5 5 5 0 0 Load Engine Defaults 0 0 Reload Preset Qt::Horizontal 40 20 0 0 0 0 Name: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 300 0 Current Brush Preset. Typed in italic when the preset has unsaved settings. true 0 0 Save the current brush settings under this name &Save to Presets true 6 0 0 0 0 QFrame::StyledPanel QFrame::Raised 0 0 250 300 Fill preset area with current icon Fill area with gradient Fill area with background color Reset area to white 0 0 35 20 0 0 35 30 Settings 0 0 35 30 Scratchpad true 0 0 Current Brush Name 0 20 Qt::Horizontal 40 20 0 0 Engine: 0 0 0 0 View 0 0 0 350 1677215 1677215 presetsContainer brushEditorSettingsControls scratchpadControls - KisScratchPad QWidget
kis_scratch_pad.h
1
KisPresetSelectorStrip QWidget
kis_preset_selector_strip.h
1
KisLodAvailabilityWidget QWidget
kis_lod_availability_widget.h
1
KisHighlightedToolButton QWidget
kis_highlighted_button.h
1
diff --git a/libs/ui/input/wintab/qxcbconnection.cpp b/libs/ui/input/wintab/qxcbconnection.cpp index 4874803a4f..d597e62247 100644 --- a/libs/ui/input/wintab/qxcbconnection.cpp +++ b/libs/ui/input/wintab/qxcbconnection.cpp @@ -1,785 +1,790 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qxcbconnection_xi2.h" #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(lcQpaXInput, "qt.qpa.input") Q_LOGGING_CATEGORY(lcQpaXInputDevices, "qt.qpa.input.devices") Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen") QXcbConnection::QXcbConnection(bool canGrabServer, const char *displayName) : m_connection(0) , m_canGrabServer(canGrabServer) , m_displayName(displayName ? QByteArray(displayName) : qgetenv("DISPLAY")) #ifdef XCB_USE_XLIB , m_xlib_display(0) #endif { m_connection = QX11Info::connection(); m_xlib_display = QX11Info::display(); if (!m_connection || xcb_connection_has_error(m_connection)) { qFatal("QXcbConnection: Could not connect to display %s", m_displayName.constData()); } initializeAllAtoms(); #if defined(XCB_USE_XINPUT2) initializeXInput2(); #endif } QXcbConnection::~QXcbConnection() { #if defined(XCB_USE_XINPUT2) finalizeXInput2(); #endif } QXcbAtom::Atom QXcbConnection::qatom(xcb_atom_t xatom) const { return static_cast(std::find(m_allAtoms, m_allAtoms + QXcbAtom::NAtoms, xatom) - m_allAtoms); } void *QXcbConnection::xlib_display() const { return m_xlib_display; } QByteArray QXcbConnection::atomName(xcb_atom_t atom) { if (!atom) return QByteArray(); xcb_generic_error_t *error = 0; xcb_get_atom_name_cookie_t cookie = Q_XCB_CALL(xcb_get_atom_name(xcb_connection(), atom)); xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(xcb_connection(), cookie, &error); if (error) { qWarning() << "QXcbConnection::atomName: bad Atom" << atom; free(error); } if (reply) { QByteArray result(xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply)); free(reply); return result; } return QByteArray(); } static const char * xcb_atomnames = { // window-manager <-> client protocols "WM_PROTOCOLS\0" "WM_DELETE_WINDOW\0" "WM_TAKE_FOCUS\0" "_NET_WM_PING\0" "_NET_WM_CONTEXT_HELP\0" "_NET_WM_SYNC_REQUEST\0" "_NET_WM_SYNC_REQUEST_COUNTER\0" "MANAGER\0" "_NET_SYSTEM_TRAY_OPCODE\0" // ICCCM window state "WM_STATE\0" "WM_CHANGE_STATE\0" "WM_CLASS\0" "WM_NAME\0" // Session management "WM_CLIENT_LEADER\0" "WM_WINDOW_ROLE\0" "SM_CLIENT_ID\0" // Clipboard "CLIPBOARD\0" "INCR\0" "TARGETS\0" "MULTIPLE\0" "TIMESTAMP\0" "SAVE_TARGETS\0" "CLIP_TEMPORARY\0" "_QT_SELECTION\0" "_QT_CLIPBOARD_SENTINEL\0" "_QT_SELECTION_SENTINEL\0" "CLIPBOARD_MANAGER\0" "RESOURCE_MANAGER\0" "_XSETROOT_ID\0" "_QT_SCROLL_DONE\0" "_QT_INPUT_ENCODING\0" "_QT_CLOSE_CONNECTION\0" "_MOTIF_WM_HINTS\0" "DTWM_IS_RUNNING\0" "ENLIGHTENMENT_DESKTOP\0" "_DT_SAVE_MODE\0" "_SGI_DESKS_MANAGER\0" // EWMH (aka NETWM) "_NET_SUPPORTED\0" "_NET_VIRTUAL_ROOTS\0" "_NET_WORKAREA\0" "_NET_MOVERESIZE_WINDOW\0" "_NET_WM_MOVERESIZE\0" "_NET_WM_NAME\0" "_NET_WM_ICON_NAME\0" "_NET_WM_ICON\0" "_NET_WM_PID\0" "_NET_WM_WINDOW_OPACITY\0" "_NET_WM_STATE\0" "_NET_WM_STATE_ABOVE\0" "_NET_WM_STATE_BELOW\0" "_NET_WM_STATE_FULLSCREEN\0" "_NET_WM_STATE_MAXIMIZED_HORZ\0" "_NET_WM_STATE_MAXIMIZED_VERT\0" "_NET_WM_STATE_MODAL\0" "_NET_WM_STATE_STAYS_ON_TOP\0" "_NET_WM_STATE_DEMANDS_ATTENTION\0" "_NET_WM_USER_TIME\0" "_NET_WM_USER_TIME_WINDOW\0" "_NET_WM_FULL_PLACEMENT\0" "_NET_WM_WINDOW_TYPE\0" "_NET_WM_WINDOW_TYPE_DESKTOP\0" "_NET_WM_WINDOW_TYPE_DOCK\0" "_NET_WM_WINDOW_TYPE_TOOLBAR\0" "_NET_WM_WINDOW_TYPE_MENU\0" "_NET_WM_WINDOW_TYPE_UTILITY\0" "_NET_WM_WINDOW_TYPE_SPLASH\0" "_NET_WM_WINDOW_TYPE_DIALOG\0" "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0" "_NET_WM_WINDOW_TYPE_POPUP_MENU\0" "_NET_WM_WINDOW_TYPE_TOOLTIP\0" "_NET_WM_WINDOW_TYPE_NOTIFICATION\0" "_NET_WM_WINDOW_TYPE_COMBO\0" "_NET_WM_WINDOW_TYPE_DND\0" "_NET_WM_WINDOW_TYPE_NORMAL\0" "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\0" "_KDE_NET_WM_FRAME_STRUT\0" "_NET_FRAME_EXTENTS\0" "_NET_STARTUP_INFO\0" "_NET_STARTUP_INFO_BEGIN\0" "_NET_SUPPORTING_WM_CHECK\0" "_NET_WM_CM_S0\0" "_NET_SYSTEM_TRAY_VISUAL\0" "_NET_ACTIVE_WINDOW\0" // Property formats "TEXT\0" "UTF8_STRING\0" "CARDINAL\0" // xdnd "XdndEnter\0" "XdndPosition\0" "XdndStatus\0" "XdndLeave\0" "XdndDrop\0" "XdndFinished\0" "XdndTypeList\0" "XdndActionList\0" "XdndSelection\0" "XdndAware\0" "XdndProxy\0" "XdndActionCopy\0" "XdndActionLink\0" "XdndActionMove\0" "XdndActionPrivate\0" // Motif DND "_MOTIF_DRAG_AND_DROP_MESSAGE\0" "_MOTIF_DRAG_INITIATOR_INFO\0" "_MOTIF_DRAG_RECEIVER_INFO\0" "_MOTIF_DRAG_WINDOW\0" "_MOTIF_DRAG_TARGETS\0" "XmTRANSFER_SUCCESS\0" "XmTRANSFER_FAILURE\0" // Xkb "_XKB_RULES_NAMES\0" // XEMBED "_XEMBED\0" "_XEMBED_INFO\0" // XInput2 "Button Left\0" "Button Middle\0" "Button Right\0" "Button Wheel Up\0" "Button Wheel Down\0" "Button Horiz Wheel Left\0" "Button Horiz Wheel Right\0" "Abs MT Position X\0" "Abs MT Position Y\0" "Abs MT Touch Major\0" "Abs MT Touch Minor\0" "Abs MT Pressure\0" "Abs MT Tracking ID\0" "Max Contacts\0" "Rel X\0" "Rel Y\0" // XInput2 tablet "Abs X\0" "Abs Y\0" "Abs Pressure\0" "Abs Tilt X\0" "Abs Tilt Y\0" "Abs Wheel\0" "Abs Distance\0" "Wacom Serial IDs\0" "INTEGER\0" "Rel Horiz Wheel\0" "Rel Vert Wheel\0" "Rel Horiz Scroll\0" "Rel Vert Scroll\0" "_XSETTINGS_SETTINGS\0" "_COMPIZ_DECOR_PENDING\0" "_COMPIZ_DECOR_REQUEST\0" "_COMPIZ_DECOR_DELETE_PIXMAP\0" // \0\0 terminates loop. }; void QXcbConnection::initializeAllAtoms() { const char *names[QXcbAtom::NAtoms]; const char *ptr = xcb_atomnames; int i = 0; while (*ptr) { names[i++] = ptr; while (*ptr) ++ptr; ++ptr; } Q_ASSERT(i == QXcbAtom::NPredefinedAtoms); QByteArray settings_atom_name("_QT_SETTINGS_TIMESTAMP_"); settings_atom_name += m_displayName; names[i++] = settings_atom_name; xcb_intern_atom_cookie_t cookies[QXcbAtom::NAtoms]; Q_ASSERT(i == QXcbAtom::NAtoms); for (i = 0; i < QXcbAtom::NAtoms; ++i) cookies[i] = xcb_intern_atom(xcb_connection(), false, strlen(names[i]), names[i]); for (i = 0; i < QXcbAtom::NAtoms; ++i) { xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xcb_connection(), cookies[i], 0); m_allAtoms[i] = reply->atom; free(reply); } } bool QXcbConnection::xi2MouseEvents() const { static bool mouseViaXI2 = !qEnvironmentVariableIsSet("QT_XCB_NO_XI2_MOUSE"); return mouseViaXI2; } void QXcbConnection::notifyEnterEvent(xcb_enter_notify_event_t *event) { xcb_window_t window; // first cleaning up deleted windows: assuming 0 is not a valid window id while ((window = m_windowMapper.key(0,0)) != 0) { m_windowMapper.remove(window); } addWindowFromXi2Id(event->event); } void QXcbConnection::addWindowFromXi2Id(xcb_window_t id) { if (!m_windowMapper.contains(id)) { QWidget *widget = QWidget::find(id); if (widget) { QWindow *windowHandle = widget->windowHandle(); m_windowMapper.insert(id, windowHandle); } } } QWindow* QXcbConnection::windowFromId(xcb_window_t id) { QWindow *window = m_windowMapper.value(id, 0); // Try to fetch the window Id lazily. It is needed when the cursor gets under // a popup window or a popup dialog, which doesn't produce any enter event on // some systems if (!window) { addWindowFromXi2Id(id); window = m_windowMapper.value(id, 0); } return window; } static int xi2ValuatorOffset(unsigned char *maskPtr, int maskLen, int number) { int offset = 0; for (int i = 0; i < maskLen; i++) { if (number < 8) { if ((maskPtr[i] & (1 << number)) == 0) return -1; } for (int j = 0; j < 8; j++) { if (j == number) return offset; if (maskPtr[i] & (1 << j)) offset++; } number -= 8; } return -1; } bool QXcbConnection::xi2GetValuatorValueIfSet(void *event, int valuatorNum, double *value) { xXIDeviceEvent *xideviceevent = static_cast(event); unsigned char *buttonsMaskAddr = (unsigned char*)&xideviceevent[1]; unsigned char *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4; FP3232 *valuatorsValuesAddr = (FP3232*)(valuatorsMaskAddr + xideviceevent->valuators_len * 4); int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum); if (valuatorOffset < 0) return false; *value = valuatorsValuesAddr[valuatorOffset].integral; *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16)); return true; } // Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed: // - "pad0" became "extension" // - "pad1" and "pad" became "pad0" // New and old version of this struct share the following fields: // NOTE: API might change again in the next release of xcb in which case this comment will // need to be updated to reflect the reality. typedef struct qt_xcb_ge_event_t { uint8_t response_type; uint8_t extension; uint16_t sequence; uint32_t length; uint16_t event_type; } qt_xcb_ge_event_t; bool QXcbConnection::xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *ev, int opCode) { qt_xcb_ge_event_t *event = (qt_xcb_ge_event_t *)ev; // xGenericEvent has "extension" on the second byte, the same is true for xcb_ge_event_t starting from // the xcb version 1.9.3, prior to that it was called "pad0". if (event->extension == opCode) { // xcb event structs contain stuff that wasn't on the wire, the full_sequence field // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes. // Move this data back to have the same layout in memory as it was on the wire // and allow casting, overwriting the full_sequence field. memmove((char*) event + 32, (char*) event + 36, event->length * 4); return true; } return false; } class Q_GUI_EXPORT QWindowSystemInterfacePrivate { public: enum EventType { UserInputEvent = 0x100, Close = UserInputEvent | 0x01, GeometryChange = 0x02, Enter = UserInputEvent | 0x03, Leave = UserInputEvent | 0x04, ActivatedWindow = 0x05, WindowStateChanged = 0x06, Mouse = UserInputEvent | 0x07, FrameStrutMouse = UserInputEvent | 0x08, Wheel = UserInputEvent | 0x09, Key = UserInputEvent | 0x0a, Touch = UserInputEvent | 0x0b, ScreenOrientation = 0x0c, ScreenGeometry = 0x0d, ScreenAvailableGeometry = 0x0e, ScreenLogicalDotsPerInch = 0x0f, ScreenRefreshRate = 0x10, ThemeChange = 0x11, Expose_KRITA_XXX = 0x12, FileOpen = UserInputEvent | 0x13, Tablet = UserInputEvent | 0x14, TabletEnterProximity = UserInputEvent | 0x15, TabletLeaveProximity = UserInputEvent | 0x16, PlatformPanel = UserInputEvent | 0x17, ContextMenu = UserInputEvent | 0x18, EnterWhatsThisMode = UserInputEvent | 0x19, #ifndef QT_NO_GESTURES Gesture = UserInputEvent | 0x1a, #endif ApplicationStateChanged = 0x19, FlushEvents = 0x20, WindowScreenChanged = 0x21 }; class WindowSystemEvent { public: enum { Synthetic = 0x1, NullWindow = 0x2 }; explicit WindowSystemEvent(EventType t) : type(t), flags(0) { } virtual ~WindowSystemEvent() { } bool synthetic() const { return flags & Synthetic; } bool nullWindow() const { return flags & NullWindow; } EventType type; int flags; }; class UserEvent : public WindowSystemEvent { public: UserEvent(QWindow * w, ulong time, EventType t) : WindowSystemEvent(t), window(w), timestamp(time) { if (!w) flags |= NullWindow; } QPointer window; unsigned long timestamp; }; class InputEvent: public UserEvent { public: InputEvent(QWindow * w, ulong time, EventType t, Qt::KeyboardModifiers mods) : UserEvent(w, time, t), modifiers(mods) {} Qt::KeyboardModifiers modifiers; }; class TabletEvent : public InputEvent { public: static void handleTabletEvent(QWindow *w, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); TabletEvent(QWindow *w, ulong time, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons b, qreal pressure, int xTilt, int yTilt, qreal tpressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers mods) : InputEvent(w, time, Tablet, mods), buttons(b), local(local), global(global), device(device), pointerType(pointerType), pressure(pressure), xTilt(xTilt), yTilt(yTilt), tangentialPressure(tpressure), rotation(rotation), z(z), uid(uid) { } Qt::MouseButtons buttons; QPointF local; QPointF global; int device; int pointerType; qreal pressure; int xTilt; int yTilt; qreal tangentialPressure; qreal rotation; int z; qint64 uid; }; class WheelEvent : public InputEvent { public: +#if QT_VERSION >= 0x050700 + WheelEvent(QWindow *w, ulong time, const QPointF & local, const QPointF & global, QPoint pixelD, QPoint angleD, int qt4D, Qt::Orientation qt4O, + Qt::KeyboardModifiers mods, Qt::ScrollPhase phase = Qt::NoScrollPhase, Qt::MouseEventSource src = Qt::MouseEventNotSynthesized) +#else WheelEvent(QWindow *w, ulong time, const QPointF & local, const QPointF & global, QPoint pixelD, QPoint angleD, int qt4D, Qt::Orientation qt4O, Qt::KeyboardModifiers mods, Qt::ScrollPhase phase = Qt::ScrollUpdate, Qt::MouseEventSource src = Qt::MouseEventNotSynthesized) +#endif : InputEvent(w, time, Wheel, mods), pixelDelta(pixelD), angleDelta(angleD), qt4Delta(qt4D), qt4Orientation(qt4O), localPos(local), globalPos(global), phase(phase), source(src) { } QPoint pixelDelta; QPoint angleDelta; int qt4Delta; Qt::Orientation qt4Orientation; QPointF localPos; QPointF globalPos; Qt::ScrollPhase phase; Qt::MouseEventSource source; }; }; void processTabletEvent(QWindowSystemInterfacePrivate::TabletEvent *e); static QElapsedTimer g_eventTimer; struct EventTimerStaticInitializer { EventTimerStaticInitializer() { g_eventTimer.start(); } }; EventTimerStaticInitializer __timerStaticInitializer; Qt::MouseButtons tabletState = Qt::NoButton; QPointer tabletPressWidget = 0; void QWindowSystemInterface::handleTabletEvent(QWindow *w, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) { qint64 timestamp = g_eventTimer.elapsed(); QWindowSystemInterfacePrivate::TabletEvent *e = new QWindowSystemInterfacePrivate::TabletEvent(w, timestamp, local, global, device, pointerType, buttons, pressure, xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); processTabletEvent(e); } void processTabletEvent(QWindowSystemInterfacePrivate::TabletEvent *e) { #ifndef QT_NO_TABLETEVENT QEvent::Type type = QEvent::TabletMove; if (e->buttons != tabletState) type = (e->buttons > tabletState) ? QEvent::TabletPress : QEvent::TabletRelease; bool localValid = true; // It can happen that we got no tablet release event. Just catch it here // and clean up the state. if (type == QEvent::TabletMove && e->buttons == Qt::NoButton) { tabletPressWidget = 0; } QWidget *targetWidget = 0; if (tabletPressWidget) { targetWidget = tabletPressWidget; localValid = false; } else if (e->window) { /** * Here we use a weird way of converting QWindow into a * QWidget. The problem is that the Qt itself does it by just * converting QWindow into QWidgetWindow. But the latter one * is private, so we cannot use it. * * We also cannot use QApplication::widegtAt(). We *MUST NOT*! * There is some but in XCB: if we call * QApplication::topLevelAt() during the event processing, the * Enter/Leave events stop arriving. Or, more precisely, they * start to errive at random points in time. Which makes * KisShortcutMatcher go crazy of course. * * So instead of just fetching the toplevel window we decrypt * the pointer using WinId mapping. */ targetWidget = QWidget::find(e->window->winId()); if (targetWidget) { QWidget *childWidget = targetWidget->childAt(e->local.toPoint()); if (childWidget) { targetWidget = childWidget; localValid = false; } } } if (!targetWidget) { targetWidget = QApplication::widgetAt(e->global.toPoint()); localValid = false; if (!targetWidget) return; } if (type == QEvent::TabletPress) { tabletPressWidget = targetWidget; } else if (type == QEvent::TabletRelease) { tabletPressWidget = 0; } QPointF local = e->local; if (!localValid) { QPointF delta = e->global - e->global.toPoint(); local = targetWidget->mapFromGlobal(e->global.toPoint()) + delta; } Qt::MouseButtons stateChange = e->buttons ^ tabletState; Qt::MouseButton button = Qt::NoButton; for (int check = Qt::LeftButton; check <= int(Qt::MaxMouseButton); check = check << 1) { if (check & stateChange) { button = Qt::MouseButton(check); break; } } QTabletEvent ev(type, local, e->global, e->device, e->pointerType, e->pressure, e->xTilt, e->yTilt, e->tangentialPressure, e->rotation, e->z, e->modifiers, e->uid, button, e->buttons); ev.setTimestamp(e->timestamp); QGuiApplication::sendEvent(targetWidget, &ev); tabletState = e->buttons; #else Q_UNUSED(e) #endif } void QWindowSystemInterface::handleTabletEnterProximityEvent(int device, int pointerType, qint64 uid) { qint64 timestamp = g_eventTimer.elapsed(); QTabletEvent ev(QEvent::TabletEnterProximity, QPointF(), QPointF(), device, pointerType, 0, 0, 0, 0, 0, 0, Qt::NoModifier, uid, Qt::NoButton, tabletState); ev.setTimestamp(timestamp); QGuiApplication::sendEvent(qGuiApp, &ev); } void QWindowSystemInterface::handleTabletLeaveProximityEvent(int device, int pointerType, qint64 uid) { qint64 timestamp = g_eventTimer.elapsed(); QTabletEvent ev(QEvent::TabletLeaveProximity, QPointF(), QPointF(), device, pointerType, 0, 0, 0, 0, 0, 0, Qt::NoModifier, uid, Qt::NoButton, tabletState); ev.setTimestamp(timestamp); QGuiApplication::sendEvent(qGuiApp, &ev); } void processWheelEvent(QWindowSystemInterfacePrivate::WheelEvent *e); void QWindowSystemInterface::handleWheelEvent(QWindow *tlw, ulong timestamp, const QPointF & local, const QPointF & global, QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods, Qt::ScrollPhase phase, Qt::MouseEventSource source) { // Qt 4 sends two separate wheel events for horizontal and vertical // deltas. For Qt 5 we want to send the deltas in one event, but at the // same time preserve source and behavior compatibility with Qt 4. // // In addition high-resolution pixel-based deltas are also supported. // Platforms that does not support these may pass a null point here. // Angle deltas must always be sent in addition to pixel deltas. QScopedPointer e; // Pass Qt::ScrollBegin and Qt::ScrollEnd through // even if the wheel delta is null. if (angleDelta.isNull() && phase == Qt::ScrollUpdate) return; // Simple case: vertical deltas only: if (angleDelta.y() != 0 && angleDelta.x() == 0) { e.reset(new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, pixelDelta, angleDelta, angleDelta.y(), Qt::Vertical, mods, phase, source)); processWheelEvent(e.data()); return; } // Simple case: horizontal deltas only: if (angleDelta.y() == 0 && angleDelta.x() != 0) { e.reset(new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, pixelDelta, angleDelta, angleDelta.x(), Qt::Horizontal, mods, phase, source)); processWheelEvent(e.data()); return; } // Both horizontal and vertical deltas: Send two wheel events. // The first event contains the Qt 5 pixel and angle delta as points, // and in addition the Qt 4 compatibility vertical angle delta. e.reset(new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, pixelDelta, angleDelta, angleDelta.y(), Qt::Vertical, mods, phase, source)); processWheelEvent(e.data()); // The second event contains null pixel and angle points and the // Qt 4 compatibility horizontal angle delta. e.reset(new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, QPoint(), QPoint(), angleDelta.x(), Qt::Horizontal, mods, phase, source)); processWheelEvent(e.data()); } void processWheelEvent(QWindowSystemInterfacePrivate::WheelEvent *e) { #ifndef QT_NO_WHEELEVENT QWindow *window = e->window.data(); QPointF globalPoint = e->globalPos; QPointF localPoint = e->localPos; if (e->nullWindow()) { window = QGuiApplication::topLevelAt(globalPoint.toPoint()); if (window) { QPointF delta = globalPoint - globalPoint.toPoint(); localPoint = window->mapFromGlobal(globalPoint.toPoint()) + delta; } } if (!window) return; // Cut off in Krita... // // QGuiApplicationPrivate::lastCursorPosition = globalPoint; // modifier_buttons = e->modifiers; //if (window->d_func()->blockedByModalWindow) { if (QGuiApplication::modalWindow() && QGuiApplication::modalWindow() != window) { // a modal window is blocking this window, don't allow wheel events through return; } #if QT_VERSION >= 0x050500 QWheelEvent ev(localPoint, globalPoint, e->pixelDelta, e->angleDelta, e->qt4Delta, e->qt4Orientation, QGuiApplication::mouseButtons(), e->modifiers, e->phase, e->source); #else QWheelEvent ev(localPoint, globalPoint, e->pixelDelta, e->angleDelta, e->qt4Delta, e->qt4Orientation, QGuiApplication::mouseButtons(), e->modifiers, e->phase); #endif ev.setTimestamp(e->timestamp); QGuiApplication::sendEvent(window, &ev); #endif /* ifndef QT_NO_WHEELEVENT */ } diff --git a/libs/ui/input/wintab/qxcbconnection_xi2.h b/libs/ui/input/wintab/qxcbconnection_xi2.h index c900679052..8a1d21c1a5 100644 --- a/libs/ui/input/wintab/qxcbconnection_xi2.h +++ b/libs/ui/input/wintab/qxcbconnection_xi2.h @@ -1,453 +1,457 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #define XCB_USE_XINPUT2 1 #define XCB_USE_XLIB 1 // This is needed to make Qt compile together with XKB. xkb.h is using a variable // which is called 'explicit', this is a reserved keyword in c++ #ifndef QT_NO_XKB #define explicit dont_use_cxx_explicit //#include #undef explicit #endif #ifndef QT_NO_TABLETEVENT #include #endif #if XCB_USE_XINPUT2 #include #include #ifdef XIScrollClass #define XCB_USE_XINPUT21 // XI 2.1 adds smooth scrolling support #ifdef XI_TouchBeginMask #define XCB_USE_XINPUT22 // XI 2.2 adds multi-point touch support #endif #endif #endif // XCB_USE_XINPUT2 struct xcb_randr_get_output_info_reply_t; struct XInput2TouchDeviceData; Q_DECLARE_LOGGING_CATEGORY(lcQpaXInput) Q_DECLARE_LOGGING_CATEGORY(lcQpaXInputDevices) Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen) class QWindowSystemInterface { public: struct TouchPoint { TouchPoint() : id(0), pressure(0), state(Qt::TouchPointStationary), flags(0) { } int id; // for application use QPointF normalPosition; // touch device coordinates, (0 to 1, 0 to 1) QRectF area; // the touched area, centered at position in screen coordinates qreal pressure; // 0 to 1 Qt::TouchPointState state; //Qt::TouchPoint{Pressed|Moved|Stationary|Released} QVector2D velocity; // in screen coordinate system, pixels / seconds QTouchEvent::TouchPoint::InfoFlags flags; QVector rawPositions; // in screen coordinates }; static void registerTouchDevice(QTouchDevice *device) { Q_UNUSED(device); } static void handleTouchEvent(QWindow *w, ulong timestamp, QTouchDevice *device, const QList &points, Qt::KeyboardModifiers mods = Qt::NoModifier) { Q_UNUSED(w); Q_UNUSED(timestamp); Q_UNUSED(device); Q_UNUSED(points); Q_UNUSED(mods); ENTER_FUNCTION(); } +#if QT_VERSION >= 0x050700 + static void handleWheelEvent(QWindow *w, ulong timestamp, const QPointF & local, const QPointF & global, QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods = Qt::NoModifier, Qt::ScrollPhase phase = Qt::NoScrollPhase, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); +#else static void handleWheelEvent(QWindow *w, ulong timestamp, const QPointF & local, const QPointF & global, QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods = Qt::NoModifier, Qt::ScrollPhase phase = Qt::ScrollUpdate, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); +#endif static void handleTabletEnterProximityEvent(int device, int pointerType, qint64 uid); static void handleTabletLeaveProximityEvent(int device, int pointerType, qint64 uid); static void handleTabletEvent(QWindow *w, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers); }; namespace QXcbAtom { enum Atom { // window-manager <-> client protocols WM_PROTOCOLS, WM_DELETE_WINDOW, WM_TAKE_FOCUS, _NET_WM_PING, _NET_WM_CONTEXT_HELP, _NET_WM_SYNC_REQUEST, _NET_WM_SYNC_REQUEST_COUNTER, MANAGER, // System tray notification _NET_SYSTEM_TRAY_OPCODE, // System tray operation // ICCCM window state WM_STATE, WM_CHANGE_STATE, WM_CLASS, WM_NAME, // Session management WM_CLIENT_LEADER, WM_WINDOW_ROLE, SM_CLIENT_ID, // Clipboard CLIPBOARD, INCR, TARGETS, MULTIPLE, TIMESTAMP, SAVE_TARGETS, CLIP_TEMPORARY, _QT_SELECTION, _QT_CLIPBOARD_SENTINEL, _QT_SELECTION_SENTINEL, CLIPBOARD_MANAGER, RESOURCE_MANAGER, _XSETROOT_ID, _QT_SCROLL_DONE, _QT_INPUT_ENCODING, // Qt/XCB specific _QT_CLOSE_CONNECTION, _MOTIF_WM_HINTS, DTWM_IS_RUNNING, ENLIGHTENMENT_DESKTOP, _DT_SAVE_MODE, _SGI_DESKS_MANAGER, // EWMH (aka NETWM) _NET_SUPPORTED, _NET_VIRTUAL_ROOTS, _NET_WORKAREA, _NET_MOVERESIZE_WINDOW, _NET_WM_MOVERESIZE, _NET_WM_NAME, _NET_WM_ICON_NAME, _NET_WM_ICON, _NET_WM_PID, _NET_WM_WINDOW_OPACITY, _NET_WM_STATE, _NET_WM_STATE_ABOVE, _NET_WM_STATE_BELOW, _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MODAL, _NET_WM_STATE_STAYS_ON_TOP, _NET_WM_STATE_DEMANDS_ATTENTION, _NET_WM_USER_TIME, _NET_WM_USER_TIME_WINDOW, _NET_WM_FULL_PLACEMENT, _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DESKTOP, _NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_TOOLBAR, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_UTILITY, _NET_WM_WINDOW_TYPE_SPLASH, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, _NET_WM_WINDOW_TYPE_POPUP_MENU, _NET_WM_WINDOW_TYPE_TOOLTIP, _NET_WM_WINDOW_TYPE_NOTIFICATION, _NET_WM_WINDOW_TYPE_COMBO, _NET_WM_WINDOW_TYPE_DND, _NET_WM_WINDOW_TYPE_NORMAL, _KDE_NET_WM_WINDOW_TYPE_OVERRIDE, _KDE_NET_WM_FRAME_STRUT, _NET_FRAME_EXTENTS, _NET_STARTUP_INFO, _NET_STARTUP_INFO_BEGIN, _NET_SUPPORTING_WM_CHECK, _NET_WM_CM_S0, _NET_SYSTEM_TRAY_VISUAL, _NET_ACTIVE_WINDOW, // Property formats TEXT, UTF8_STRING, CARDINAL, // Xdnd XdndEnter, XdndPosition, XdndStatus, XdndLeave, XdndDrop, XdndFinished, XdndTypelist, XdndActionList, XdndSelection, XdndAware, XdndProxy, XdndActionCopy, XdndActionLink, XdndActionMove, XdndActionPrivate, // Motif DND _MOTIF_DRAG_AND_DROP_MESSAGE, _MOTIF_DRAG_INITIATOR_INFO, _MOTIF_DRAG_RECEIVER_INFO, _MOTIF_DRAG_WINDOW, _MOTIF_DRAG_TARGETS, XmTRANSFER_SUCCESS, XmTRANSFER_FAILURE, // Xkb _XKB_RULES_NAMES, // XEMBED _XEMBED, _XEMBED_INFO, // XInput2 ButtonLeft, ButtonMiddle, ButtonRight, ButtonWheelUp, ButtonWheelDown, ButtonHorizWheelLeft, ButtonHorizWheelRight, AbsMTPositionX, AbsMTPositionY, AbsMTTouchMajor, AbsMTTouchMinor, AbsMTPressure, AbsMTTrackingID, MaxContacts, RelX, RelY, // XInput2 tablet AbsX, AbsY, AbsPressure, AbsTiltX, AbsTiltY, AbsWheel, AbsDistance, WacomSerialIDs, INTEGER, RelHorizWheel, RelVertWheel, RelHorizScroll, RelVertScroll, _XSETTINGS_SETTINGS, _COMPIZ_DECOR_PENDING, _COMPIZ_DECOR_REQUEST, _COMPIZ_DECOR_DELETE_PIXMAP, NPredefinedAtoms, _QT_SETTINGS_TIMESTAMP = NPredefinedAtoms, NAtoms }; } class QXcbConnection { public: struct ScrollingDevice { ScrollingDevice() : deviceId(0), verticalIndex(0), horizontalIndex(0), orientations(0), legacyOrientations(0) { } int deviceId; int verticalIndex, horizontalIndex; double verticalIncrement, horizontalIncrement; Qt::Orientations orientations; Qt::Orientations legacyOrientations; QPointF lastScrollPosition; }; struct TabletData { TabletData() : deviceId(0), pointerType(QTabletEvent::UnknownPointer), tool(QTabletEvent::Stylus), buttons(0), serialId(0), inProximity(false) { } int deviceId; QTabletEvent::PointerType pointerType; QTabletEvent::TabletDevice tool; Qt::MouseButtons buttons; qint64 serialId; bool inProximity; struct ValuatorClassInfo { ValuatorClassInfo() : minVal(0.), maxVal(0.), curVal(0.) { } double minVal; double maxVal; double curVal; int number; }; QHash valuatorInfo; }; public: QXcbConnection(bool canGrabServer, const char *displayName); ~QXcbConnection(); #ifdef XCB_USE_XINPUT21 bool isAtLeastXI21() const { return m_xi2Enabled && m_xi2Minor >= 1; } #else bool isAtLeastXI21() const { return false; } #endif #ifdef XCB_USE_XINPUT22 bool isAtLeastXI22() const { return m_xi2Enabled && m_xi2Minor >= 2; } #else bool isAtLeastXI22() const { return false; } #endif void initializeXInput2(); void xi2SetupDevices(); void finalizeXInput2(); void xi2Select(xcb_window_t window); XInput2TouchDeviceData *touchDeviceForId(int id); bool xi2HandleEvent(xcb_ge_event_t *event); bool xi2SetMouseGrabEnabled(xcb_window_t w, bool grab); static bool xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *event, int opCode); // FIXME: to be copied static bool xi2GetValuatorValueIfSet(void *event, int valuatorNum, double *value); // FIXME: to be copied void xi2HandleHierachyEvent(void *event); void xi2HandleDeviceChangedEvent(void *event); void updateScrollingDevice(ScrollingDevice &scrollingDevice, int num_classes, void *classInfo); void handleEnterEvent(const xcb_enter_notify_event_t *); void xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice); Qt::MouseButton xiToQtMouseButton(uint32_t b); Qt::MouseButtons xiToQtMouseButtons(xXIDeviceEvent *xiDeviceEvent); bool xi2HandleTabletEvent(void *event, TabletData *tabletData, QWindow *window); void xi2ReportTabletEvent(TabletData &tabletData, void *event); inline xcb_atom_t atom(QXcbAtom::Atom atom) const { return m_allAtoms[atom]; } QXcbAtom::Atom qatom(xcb_atom_t xatom) const; QByteArray atomName(xcb_atom_t atom); void initializeAllAtoms(); bool xi2MouseEvents() const; QWindow *windowFromId(xcb_window_t id); bool canGrab() const { return m_canGrabServer; } void *xlib_display() const; xcb_connection_t *xcb_connection() const { return m_connection; } void notifyEnterEvent(xcb_enter_notify_event_t *event); void addWindowFromXi2Id(xcb_window_t id); private: xcb_connection_t *m_connection; bool m_canGrabServer; QByteArray m_displayName; void *m_xlib_display; xcb_atom_t m_allAtoms[QXcbAtom::NAtoms]; bool m_xi2Enabled; int m_xi2Minor; int m_xiOpCode, m_xiEventBase, m_xiErrorBase; QVector m_tabletData; QHash> m_windowMapper; QHash m_touchDevices; bool m_xiGrab; QHash m_scrollingDevices; }; #ifdef Q_XCB_DEBUG template cookie_t q_xcb_call_template(const cookie_t &cookie, QXcbConnection *connection, const char *file, int line) { connection->log(file, line, cookie.sequence); return cookie; } #define Q_XCB_CALL(x) q_xcb_call_template(x, connection(), __FILE__, __LINE__) #define Q_XCB_CALL2(x, connection) q_xcb_call_template(x, connection, __FILE__, __LINE__) #define Q_XCB_NOOP(c) q_xcb_call_template(xcb_no_operation(c->xcb_connection()), c, __FILE__, __LINE__); #else #define Q_XCB_CALL(x) x #define Q_XCB_CALL2(x, connection) x #define Q_XCB_NOOP(c) (void)c; #endif diff --git a/libs/ui/kis_animation_cache_populator.cpp b/libs/ui/kis_animation_cache_populator.cpp index 5e83991a0b..952ce7231f 100644 --- a/libs/ui/kis_animation_cache_populator.cpp +++ b/libs/ui/kis_animation_cache_populator.cpp @@ -1,312 +1,309 @@ /* * 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. */ #include "kis_animation_cache_populator.h" #include #include #include #include #include "kis_config.h" #include "kis_config_notifier.h" #include "KisPart.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_canvas2.h" #include "kis_time_range.h" #include "kis_animation_frame_cache.h" #include "kis_update_info.h" #include "kis_signal_auto_connection.h" #include "kis_idle_watcher.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_keyframe_channel.h" #include "KisAnimationCacheRegenerator.h" struct KisAnimationCachePopulator::Private { KisAnimationCachePopulator *q; KisPart *part; QTimer timer; /** * Counts up the number of subsequent times Krita has been detected idle. */ int idleCounter; static const int IDLE_COUNT_THRESHOLD = 4; static const int IDLE_CHECK_INTERVAL = 500; static const int BETWEEN_FRAMES_INTERVAL = 10; int requestedFrame; KisAnimationFrameCacheSP requestCache; KisOpenGLUpdateInfoSP requestInfo; KisSignalAutoConnectionsStore imageRequestConnections; QFutureWatcher infoConversionWatcher; KisAnimationCacheRegenerator regenerator; bool calculateAnimationCacheInBackground = true; enum State { NotWaitingForAnything, WaitingForIdle, WaitingForFrame, BetweenFrames }; State state; Private(KisAnimationCachePopulator *_q, KisPart *_part) : q(_q), part(_part), idleCounter(0), requestedFrame(-1), state(WaitingForIdle) { timer.setSingleShot(true); } void timerTimeout() { switch (state) { case WaitingForIdle: case BetweenFrames: generateIfIdle(); break; case WaitingForFrame: KIS_ASSERT_RECOVER_NOOP(0 && "WaitingForFrame cannot have a timeout. Just skip this message and report a bug"); break; case NotWaitingForAnything: KIS_ASSERT_RECOVER_NOOP(0 && "NotWaitingForAnything cannot have a timeout. Just skip this message and report a bug"); break; } } void generateIfIdle() { if (part->idleWatcher()->isIdle()) { idleCounter++; if (idleCounter >= IDLE_COUNT_THRESHOLD) { if (!tryRequestGeneration()) { enterState(NotWaitingForAnything); } return; } } else { idleCounter = 0; } enterState(WaitingForIdle); } bool tryRequestGeneration() { // Prioritize the active document KisAnimationFrameCacheSP activeDocumentCache = KisAnimationFrameCacheSP(0); KisMainWindow *activeWindow = part->currentMainwindow(); if (activeWindow && activeWindow->activeView()) { KisCanvas2 *activeCanvas = activeWindow->activeView()->canvasBase(); if (activeCanvas && activeCanvas->frameCache()) { activeDocumentCache = activeCanvas->frameCache(); // Let's skip frames affected by changes to the active node (on the active document) // This avoids constant invalidation and regeneration while drawing KisNodeSP activeNode = activeCanvas->viewManager()->nodeManager()->activeNode(); KisTimeRange skipRange; if (activeNode) { int currentTime = activeCanvas->currentImage()->animationInterface()->currentUITime(); - const QList channels = - activeNode->keyframeChannels(); - - if (!channels.isEmpty()) { - Q_FOREACH (const KisKeyframeChannel *channel, channels) { + if (!activeNode->keyframeChannels().isEmpty()) { + Q_FOREACH (const KisKeyframeChannel *channel, activeNode->keyframeChannels()) { skipRange |= channel->affectedFrames(currentTime); } } else { skipRange = KisTimeRange::infinite(0); } } bool requested = tryRequestGeneration(activeDocumentCache, skipRange); if (requested) return true; } } QList caches = KisAnimationFrameCache::caches(); KisAnimationFrameCache *cache; Q_FOREACH (cache, caches) { if (cache == activeDocumentCache.data()) { // We already handled this one... continue; } bool requested = tryRequestGeneration(cache, KisTimeRange()); if (requested) return true; } return false; } bool tryRequestGeneration(KisAnimationFrameCacheSP cache, KisTimeRange skipRange) { KisImageSP image = cache->image(); if (!image) return false; KisImageAnimationInterface *animation = image->animationInterface(); KisTimeRange currentRange = animation->fullClipRange(); const int frame = KisAnimationCacheRegenerator::calcFirstDirtyFrame(cache, currentRange, skipRange); if (frame >= 0) { return regenerate(cache, frame); } return false; } bool regenerate(KisAnimationFrameCacheSP cache, int frame) { if (state == WaitingForFrame) { // Already busy, deny request return false; } /** * We should enter the state before the frame is * requested. Otherwise the signal may come earlier than we * enter it. */ enterState(WaitingForFrame); regenerator.startFrameRegeneration(frame, cache); return true; } QString debugStateToString(State newState) { QString str = ""; switch (newState) { case WaitingForIdle: str = "WaitingForIdle"; break; case WaitingForFrame: str = "WaitingForFrame"; break; case NotWaitingForAnything: str = "NotWaitingForAnything"; break; case BetweenFrames: str = "BetweenFrames"; break; } return str; } void enterState(State newState) { state = newState; int timerTimeout = -1; switch (state) { case WaitingForIdle: timerTimeout = IDLE_CHECK_INTERVAL; break; case WaitingForFrame: // the timeout is handled by the regenerator now timerTimeout = -1; break; case NotWaitingForAnything: // frame conversion cannot be cancelled, // so there is no timeout timerTimeout = -1; break; case BetweenFrames: timerTimeout = BETWEEN_FRAMES_INTERVAL; break; } if (timerTimeout >= 0) { timer.start(timerTimeout); } else { timer.stop(); } } }; KisAnimationCachePopulator::KisAnimationCachePopulator(KisPart *part) : m_d(new Private(this, part)) { connect(&m_d->timer, SIGNAL(timeout()), this, SLOT(slotTimer())); connect(&m_d->regenerator, SIGNAL(sigFrameCancelled()), SLOT(slotRegeneratorFrameCancelled())); connect(&m_d->regenerator, SIGNAL(sigFrameFinished()), SLOT(slotRegeneratorFrameReady())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); } KisAnimationCachePopulator::~KisAnimationCachePopulator() {} bool KisAnimationCachePopulator::regenerate(KisAnimationFrameCacheSP cache, int frame) { return m_d->regenerate(cache, frame); } void KisAnimationCachePopulator::slotTimer() { m_d->timerTimeout(); } void KisAnimationCachePopulator::slotRequestRegeneration() { // skip if the user forbade background regeneration if (!m_d->calculateAnimationCacheInBackground) return; m_d->enterState(Private::WaitingForIdle); } void KisAnimationCachePopulator::slotRegeneratorFrameCancelled() { KIS_ASSERT_RECOVER_RETURN(m_d->state == Private::WaitingForFrame); m_d->enterState(Private::NotWaitingForAnything); } void KisAnimationCachePopulator::slotRegeneratorFrameReady() { m_d->enterState(Private::BetweenFrames); } void KisAnimationCachePopulator::slotConfigChanged() { KisConfig cfg; m_d->calculateAnimationCacheInBackground = cfg.calculateAnimationCacheInBackground(); } diff --git a/libs/ui/kis_favorite_resource_manager.cpp b/libs/ui/kis_favorite_resource_manager.cpp index 1b8c1d03f2..eb148f6e17 100644 --- a/libs/ui/kis_favorite_resource_manager.cpp +++ b/libs/ui/kis_favorite_resource_manager.cpp @@ -1,369 +1,369 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "kis_favorite_resource_manager.h" #include "kis_popup_palette.h" #include "kis_paintop_box.h" #include "KisViewManager.h" #include "kis_resource_server_provider.h" #include "kis_min_heap.h" #include "kis_config.h" #include "kis_config_notifier.h" class KisFavoriteResourceManager::ColorDataList { public: static const int MAX_RECENT_COLOR = 12; ColorDataList() { m_key = 0; } ~ColorDataList() { qDeleteAll(m_guiList); } int size() { return m_guiList.size(); } int leastUsedGuiPos() { return findPos(m_priorityList.valueAt(0)); } const KoColor& guiColor(int pos) { Q_ASSERT_X(pos < size(), "ColorDataList::guiColor", "index out of bound"); Q_ASSERT_X(pos >= 0, "ColorDataList::guiColor", "negative index"); return m_guiList.at(pos)->data; } void append(const KoColor& data) { int pos = findPos(data); if (pos > -1) updateKey(pos); else appendNew(data); } void appendNew(const KoColor& data) { if (size() >= ColorDataList::MAX_RECENT_COLOR) removeLeastUsed(); PriorityNode * node; node = new PriorityNode (); node->data = data; node->key = m_key++; m_priorityList.append(node); int pos = guiInsertPos(data); pos >= m_guiList.size() ? m_guiList.append(node) : m_guiList.insert(pos, node); node = 0; } void removeLeastUsed() { Q_ASSERT_X(size() >= 0, "ColorDataList::removeLeastUsed", "index out of bound"); if (size() <= 0) return; int pos = findPos(m_priorityList.valueAt(0)); m_guiList.removeAt(pos); m_priorityList.remove(0); } void updateKey(int guiPos) { if (m_guiList.at(guiPos)->key == m_key - 1) return; m_priorityList.changeKey(m_guiList.at(guiPos)->pos, m_key++); } /*find position of the color on the gui list*/ int findPos(const KoColor& color) { int low = 0, high = size(), mid = 0; while (low < high) { mid = (low + high) / 2; if (hsvComparison(color, m_guiList.at(mid)->data) == 0) return mid; else if (hsvComparison(color, m_guiList.at(mid)->data) < 0) high = mid; else low = mid + 1; } return -1; } private: int m_key; int guiInsertPos(const KoColor& color) { int low = 0, high = size() - 1, mid = (low + high) / 2; while (low < high) { hsvComparison(color, m_guiList[mid]->data) == -1 ? high = mid : low = mid + 1; mid = (low + high) / 2; } if (m_guiList.size() > 0) { if (hsvComparison(color, m_guiList[mid]->data) == 1) ++mid; } return mid; } /*compares c1 and c2 based on HSV. c1 < c2, returns -1 c1 = c2, returns 0 c1 > c2, returns 1 */ int hsvComparison(const KoColor& c1, const KoColor& c2) { QColor qc1 = c1.toQColor(); QColor qc2 = c2.toQColor(); if (qc1.hue() < qc2.hue()) return -1; if (qc1.hue() > qc2.hue()) return 1; // hue is the same, ok let's compare saturation if (qc1.saturation() < qc2.saturation()) return -1; if (qc1.saturation() > qc2.saturation()) return 1; // oh, also saturation is same? if (qc1.value() < qc2.value()) return -1; if (qc1.value() > qc2.value()) return 1; // user selected two similar colors return 0; } KisMinHeap m_priorityList; QList *> m_guiList; }; KisFavoriteResourceManager::KisFavoriteResourceManager(KisPaintopBox *paintopBox) : m_paintopBox(paintopBox) , m_colorList(0) , m_blockUpdates(false) , m_initialized(false) { KisConfig cfg; m_maxPresets = cfg.favoritePresets(); m_colorList = new ColorDataList(); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(configChanged())); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(false); rServer->addObserver(this); } KisFavoriteResourceManager::~KisFavoriteResourceManager() { KisConfig cfg; cfg.writeEntry("favoritePresetsTag", m_currentTag); KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); rServer->removeObserver(this); delete m_colorList; } void KisFavoriteResourceManager::unsetResourceServer() { // ... } QVector KisFavoriteResourceManager::favoritePresetList() { init(); return m_favoritePresetsList; } QList KisFavoriteResourceManager::favoritePresetImages() { init(); QList images; Q_FOREACH (KisPaintOpPresetSP preset, m_favoritePresetsList) { if (preset) { images.append(preset->image()); } } return images; } void KisFavoriteResourceManager::setCurrentTag(const QString& tagName) { m_currentTag = tagName; updateFavoritePresets(); } void KisFavoriteResourceManager::slotChangeActivePaintop(int pos) { if (pos < 0 || pos >= m_favoritePresetsList.size()) return; KoResource* resource = const_cast(m_favoritePresetsList.at(pos).data()); m_paintopBox->resourceSelected(resource); emit hidePalettes(); } int KisFavoriteResourceManager::numFavoritePresets() { init(); return m_favoritePresetsList.size(); } //Recent Colors void KisFavoriteResourceManager::slotUpdateRecentColor(int pos) { // Do not update the key, the colour might be selected but it is not used yet. So we are not supposed // to update the colour priority when we select it. m_colorList->updateKey(pos); emit setSelectedColor(pos); emit sigSetFGColor(m_colorList->guiColor(pos)); emit hidePalettes(); } void KisFavoriteResourceManager::slotAddRecentColor(const KoColor& color) { m_colorList->append(color); int pos = m_colorList->findPos(color); emit setSelectedColor(pos); } void KisFavoriteResourceManager::slotChangeFGColorSelector(KoColor c) { emit sigChangeFGColorSelector(c); } void KisFavoriteResourceManager::removingResource(PointerType resource) { if (m_blockUpdates) { return; } if (m_favoritePresetsList.contains(resource.data())) { updateFavoritePresets(); } } void KisFavoriteResourceManager::resourceAdded(PointerType /*resource*/) { if (m_blockUpdates) { return; } updateFavoritePresets(); } void KisFavoriteResourceManager::resourceChanged(PointerType /*resource*/) { } void KisFavoriteResourceManager::setBlockUpdates(bool block) { m_blockUpdates = block; if (!block) { updateFavoritePresets(); } } void KisFavoriteResourceManager::syncTaggedResourceView() { if (m_blockUpdates) { return; } updateFavoritePresets(); } void KisFavoriteResourceManager::syncTagAddition(const QString& /*tag*/) {} void KisFavoriteResourceManager::syncTagRemoval(const QString& /*tag*/) {} int KisFavoriteResourceManager::recentColorsTotal() { return m_colorList->size(); } const KoColor& KisFavoriteResourceManager::recentColorAt(int pos) { return m_colorList->guiColor(pos); } void KisFavoriteResourceManager::slotSetBGColor(const KoColor c) { m_bgColor = c; } KoColor KisFavoriteResourceManager::bgColor() const { return m_bgColor; } bool sortPresetByName(KisPaintOpPresetSP preset1, KisPaintOpPresetSP preset2) { return preset1->name() < preset2->name(); } void KisFavoriteResourceManager::updateFavoritePresets() { m_favoritePresetsList.clear(); KisPaintOpPresetResourceServer* rServer = KisResourceServerProvider::instance()->paintOpPresetServer(false); QStringList presetFilenames = rServer->searchTag(m_currentTag); for(int i = 0; i < qMin(m_maxPresets, presetFilenames.size()); i++) { KisPaintOpPresetSP pr = rServer->resourceByFilename(presetFilenames.at(i)); m_favoritePresetsList.append(pr.data()); qSort(m_favoritePresetsList.begin(), m_favoritePresetsList.end(), sortPresetByName); } emit updatePalettes(); } void KisFavoriteResourceManager::configChanged() { KisConfig cfg; m_maxPresets = cfg.favoritePresets(); updateFavoritePresets(); } void KisFavoriteResourceManager::init() { if (!m_initialized) { m_initialized = true; KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(true); KConfigGroup group( KSharedConfig::openConfig(), "favoriteList"); QStringList oldFavoritePresets = (group.readEntry("favoritePresets")).split(',', QString::SkipEmptyParts); KisConfig cfg; m_currentTag = cfg.readEntry("favoritePresetsTag", "Block"); if (!oldFavoritePresets.isEmpty() && m_currentTag.isEmpty()) { - m_currentTag = i18n("Favorite Presets"); + m_currentTag = i18n("Number of popup palette presets shown"); Q_FOREACH ( const QString& name, oldFavoritePresets) { KisPaintOpPresetSP preset = rServer->resourceByName(name); rServer->addTag(preset.data(), m_currentTag); } rServer->tagCategoryAdded(m_currentTag); cfg.writeEntry("favoritePresets", QString()); } updateFavoritePresets(); } } diff --git a/libs/ui/kis_node_filter_proxy_model.cpp b/libs/ui/kis_node_filter_proxy_model.cpp index 2af77e4072..97dca9e4fe 100644 --- a/libs/ui/kis_node_filter_proxy_model.cpp +++ b/libs/ui/kis_node_filter_proxy_model.cpp @@ -1,165 +1,166 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_filter_proxy_model.h" #include #include "kis_node.h" #include "kis_node_model.h" #include "kis_node_manager.h" #include "kis_signal_compressor.h" #include "kis_image.h" struct KisNodeFilterProxyModel::Private { Private() : nodeModel(0), activeNodeCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) {} KisNodeModel *nodeModel; KisNodeSP pendingActiveNode; KisNodeSP activeNode; QSet acceptedLabels; KisSignalCompressor activeNodeCompressor; bool isUpdatingFilter = false; bool checkIndexAllowedRecursively(QModelIndex srcIndex); }; KisNodeFilterProxyModel::KisNodeFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent), m_d(new Private) { connect(&m_d->activeNodeCompressor, SIGNAL(timeout()), SLOT(slotUpdateCurrentNodeFilter())); } KisNodeFilterProxyModel::~KisNodeFilterProxyModel() { } void KisNodeFilterProxyModel::setNodeModel(KisNodeModel *model) { m_d->nodeModel = model; setSourceModel(model); } bool KisNodeFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (m_d->isUpdatingFilter && role == KisNodeModel::ActiveRole) { return false; } return QSortFilterProxyModel::setData(index, value, role); } bool KisNodeFilterProxyModel::Private::checkIndexAllowedRecursively(QModelIndex srcIndex) { KisNodeSP node = nodeModel->nodeFromIndex(srcIndex); if (node == activeNode || acceptedLabels.contains(node->colorLabelIndex())) { return true; } bool result = false; const int numChildren = srcIndex.model()->rowCount(srcIndex); for (int i = 0; i < numChildren; i++) { QModelIndex child = srcIndex.child(i, 0); if (checkIndexAllowedRecursively(child)) { result = true; break; } } return result; } bool KisNodeFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return true; } const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); KisNodeSP node = m_d->nodeModel->nodeFromIndex(index); return !node || m_d->acceptedLabels.isEmpty() || m_d->checkIndexAllowedRecursively(index); } KisNodeSP KisNodeFilterProxyModel::nodeFromIndex(const QModelIndex &index) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return 0; } QModelIndex srcIndex = mapToSource(index); return m_d->nodeModel->nodeFromIndex(srcIndex); } QModelIndex KisNodeFilterProxyModel::indexFromNode(KisNodeSP node) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return QModelIndex(); } QModelIndex srcIndex = m_d->nodeModel->indexFromNode(node); return mapFromSource(srcIndex); } void KisNodeFilterProxyModel::setAcceptedLabels(const QList &value) { m_d->acceptedLabels = QSet::fromList(value); invalidateFilter(); } void KisNodeFilterProxyModel::setActiveNode(KisNodeSP node) { - KIS_SAFE_ASSERT_RECOVER_RETURN(node); + // the new node may temporary become null when the last layer + // of the document in removed m_d->pendingActiveNode = node; if (node && indexFromNode(node).isValid()) { m_d->activeNodeCompressor.start(); } else { slotUpdateCurrentNodeFilter(); } } void KisNodeFilterProxyModel::slotUpdateCurrentNodeFilter() { m_d->activeNode = m_d->pendingActiveNode; /** * During the filter update the model might emit "current changed" signals, * which (in their turn) will issue setData(..., KisNodeModel::ActiveRole) * call, leading to a double recursion. Which, obviously, crashes Krita. * * Right now, just blocking the KisNodeModel::ActiveRole call is the * most obvious solution for the problem. */ m_d->isUpdatingFilter = true; invalidateFilter(); m_d->isUpdatingFilter = false; } void KisNodeFilterProxyModel::unsetDummiesFacade() { m_d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0); m_d->pendingActiveNode = 0; m_d->activeNode = 0; } diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp index 429975d19d..00b7fd9876 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1336 +1,1416 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_manager.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 "KisPart.h" #include "canvas/kis_canvas2.h" #include "kis_shape_controller.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_mask_manager.h" #include "kis_group_layer.h" #include "kis_layer_manager.h" #include "kis_selection_manager.h" #include "kis_node_commands_adapter.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_processing_applicator.h" #include "kis_sequential_iterator.h" #include "kis_transaction.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_juggler_compressed.h" #include "kis_clipboard.h" #include "kis_node_dummies_graph.h" #include "kis_mimedata.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" struct KisNodeManager::Private { Private(KisNodeManager *_q, KisViewManager *v) : q(_q) , view(v) , imageView(0) , layerManager(v) , maskManager(v) , commandsAdapter(v) , nodeSelectionAdapter(new KisNodeSelectionAdapter(q)) , nodeInsertionAdapter(new KisNodeInsertionAdapter(q)) { } KisNodeManager * q; KisViewManager * view; QPointerimageView; KisLayerManager layerManager; KisMaskManager maskManager; KisNodeCommandsAdapter commandsAdapter; QScopedPointer nodeSelectionAdapter; QScopedPointer nodeInsertionAdapter; KisNodeList selectedNodes; QPointer nodeJuggler; KisNodeWSP previouslyActiveNode; bool activateNodeImpl(KisNodeSP node); QSignalMapper nodeCreationSignalMapper; QSignalMapper nodeConversionSignalMapper; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == q->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager.activateMask(0); layerManager.activateLayer(0); previouslyActiveNode = q->activeNode(); } else { previouslyActiveNode = q->activeNode(); KoShape * shape = view->document()->shapeForNode(node); KIS_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); KIS_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false); // shapeLayer->setGeometryProtected(node->userLocked()); // shapeLayer->setVisible(node->visible()); selection->setActiveLayer(shapeLayer); imageView->setCurrentNode(node); if (KisLayerSP layer = qobject_cast(node.data())) { maskManager.activateMask(0); layerManager.activateLayer(layer); } else if (KisMaskSP mask = dynamic_cast(node.data())) { maskManager.activateMask(mask); // XXX_NODE: for now, masks cannot be nested. layerManager.activateLayer(static_cast(node->parent().data())); } } return true; } KisNodeManager::KisNodeManager(KisViewManager *view) : m_d(new Private(this, view)) { connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP))); } KisNodeManager::~KisNodeManager() { delete m_d; } void KisNodeManager::setView(QPointerimageView) { m_d->maskManager.setView(imageView); m_d->layerManager.setView(imageView); if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this); m_d->imageView->image()->disconnect(this); } m_d->imageView = imageView; if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP))); connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction())); connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP, const KisNodeList&))); m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode()); } } #define NEW_LAYER_ACTION(id, layerType) \ { \ action = actionManager->createAction(id); \ m_d->nodeCreationSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeCreationSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION_2(id, layerType, exclude) \ { \ action = actionManager->createAction(id); \ action->setExcludedNodeTypes(QStringList(exclude)); \ actionManager->addAction(id, action); \ m_d->nodeConversionSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeConversionSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION(id, layerType) \ CONVERT_NODE_ACTION_2(id, layerType, layerType) void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager) { m_d->layerManager.setup(actionManager); m_d->maskManager.setup(actionCollection, actionManager); KisAction * action = actionManager->createAction("mirrorNodeX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX())); action = actionManager->createAction("mirrorNodeY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY())); action = actionManager->createAction("activateNextLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = actionManager->createAction("activatePreviousLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = actionManager->createAction("switchToPreviouslyActiveNode"); connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode())); action = actionManager->createAction("save_node_as_image"); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = actionManager->createAction("duplicatelayer"); connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode())); action = actionManager->createAction("copy_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard())); action = actionManager->createAction("cut_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard())); action = actionManager->createAction("paste_layer_from_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard())); action = actionManager->createAction("create_quick_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup())); action = actionManager->createAction("create_quick_clipping_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup())); action = actionManager->createAction("quick_ungroup"); connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup())); action = actionManager->createAction("select_all_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes())); action = actionManager->createAction("select_visible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes())); action = actionManager->createAction("select_locked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes())); action = actionManager->createAction("select_invisible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes())); action = actionManager->createAction("select_unlocked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes())); action = actionManager->createAction("new_from_visible"); connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible())); NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer"); NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer"); NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer"); NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer"); NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer"); NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer"); NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer"); NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask"); NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask"); NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask"); NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask"); NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask"); connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(createNode(const QString &))); CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer"); CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask"); CONVERT_NODE_ACTION("convert_to_animated", "animated"); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(convertNode(const QString &))); action = actionManager->createAction("isolate_layer"); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); + action = actionManager->createAction("toggle_layer_lock"); + connect(action, SIGNAL(triggered()), this, SLOT(toggleLock())); + + action = actionManager->createAction("toggle_layer_visibility"); + connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility())); + + action = actionManager->createAction("toggle_layer_alpha_lock"); + connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock())); + + action = actionManager->createAction("toggle_layer_inherit_alpha"); + connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha())); + action = actionManager->createAction("split_alpha_into_mask"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask())); action = actionManager->createAction("split_alpha_write"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite())); // HINT: we can save even when the nodes are not editable action = actionManager->createAction("split_alpha_save_merged"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryFinishIsolatedMode())); } void KisNodeManager::updateGUI() { // enable/disable all relevant actions m_d->layerManager.updateGUI(); m_d->maskManager.updateGUI(); } KisNodeSP KisNodeManager::activeNode() { if (m_d->imageView) { return m_d->imageView->currentNode(); } return 0; } KisLayerSP KisNodeManager::activeLayer() { return m_d->layerManager.activeLayer(); } const KoColorSpace* KisNodeManager::activeColorSpace() { if (m_d->maskManager.activeDevice()) { return m_d->maskManager.activeDevice()->colorSpace(); } else { Q_ASSERT(m_d->layerManager.activeLayer()); if (m_d->layerManager.activeLayer()->parentLayer()) return m_d->layerManager.activeLayer()->parentLayer()->colorSpace(); else return m_d->view->image()->colorSpace(); } } void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index) { if (parent->allowAsChild(node)) { if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) { KisSelectionMask *m = dynamic_cast(node.data()); KisLayer *l = qobject_cast(parent.data()); KisSelectionMaskSP selMask = l->selectionMask(); if (m && m->active() && l && l->selectionMask()) selMask->setActive(false); } m_d->commandsAdapter.moveNode(node, parent, index); } } void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Move Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, aboveThis); } void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Copy Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->copyNode(nodes, parent, aboveThis); } void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Add Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->addNode(nodes, parent, aboveThis); } void KisNodeManager::toggleIsolateActiveNode() { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode == image->isolatedModeRoot()) { toggleIsolateMode(false); } else { toggleIsolateMode(true); } } void KisNodeManager::toggleIsolateMode(bool checked) { KisImageWSP image = m_d->view->image(); if (checked) { KisNodeSP activeNode = this->activeNode(); // Transform and colorize masks don't have pixel data... if (activeNode->inherits("KisTransformMask") || activeNode->inherits("KisColorizeMask")) return; KIS_ASSERT_RECOVER_RETURN(activeNode); if (!image->startIsolatedMode(activeNode)) { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); action->setChecked(false); } } else { image->stopIsolatedMode(); } } void KisNodeManager::slotUpdateIsolateModeAction() { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); Q_ASSERT(action); KisNodeSP activeNode = this->activeNode(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); action->setChecked(isolatedRootNode && isolatedRootNode == activeNode); } void KisNodeManager::slotTryFinishIsolatedMode() { KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode) return; this->toggleIsolateMode(true); } void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN(activeNode); // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { m_d->layerManager.addLayer(activeNode); } else if (nodeType == "KisGroupLayer") { m_d->layerManager.addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { m_d->layerManager.addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { m_d->layerManager.addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { m_d->layerManager.addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { m_d->layerManager.addCloneLayer(activeNode); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisColorizeMask") { m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { m_d->layerManager.addFileLayer(activeNode); } } void KisNodeManager::createFromVisible() { KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild()); } KisLayerSP KisNodeManager::createPaintLayer() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } return m_d->layerManager.addLayer(activeNode); } void KisNodeManager::convertNode(const QString &nodeType) { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; if (nodeType == "KisPaintLayer") { m_d->layerManager.convertNodeToPaintLayer(activeNode); } else if (nodeType == "KisSelectionMask" || nodeType == "KisFilterMask" || nodeType == "KisTransparencyMask") { KisPaintDeviceSP copyFrom = activeNode->paintDevice() ? activeNode->paintDevice() : activeNode->projection(); m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask")); if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, true); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true); } m_d->commandsAdapter.removeNode(activeNode); m_d->commandsAdapter.endMacro(); } else { warnKrita << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node) { KIS_ASSERT_RECOVER_RETURN(node != activeNode()); if (m_d->activateNodeImpl(node)) { emit sigUiNeedChangeActiveNode(node); emit sigNodeActivated(node); nodesUpdated(); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } } void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node) { if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } void KisNodeManager::slotUiActivatedNode(KisNodeSP node) { if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathTool"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; if (node->inherits("KisShapeLayer")) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } } void KisNodeManager::nodesUpdated() { KisNodeSP node = activeNode(); if (!node) return; m_d->layerManager.layersUpdated(); m_d->maskManager.masksUpdated(); m_d->view->updateGUI(); m_d->view->selectionManager()->selectionChanged(); } KisPaintDeviceSP KisNodeManager::activePaintDevice() { return m_d->maskManager.activeMask() ? m_d->maskManager.activeDevice() : m_d->layerManager.activeDevice(); } void KisNodeManager::nodeProperties(KisNodeSP node) { if (selectedNodes().size() > 1 || node->inherits("KisLayer")) { m_d->layerManager.layerProperties(); } else if (node->inherits("KisMask")) { m_d->maskManager.maskProperties(); } } qint32 KisNodeManager::convertOpacityToInt(qreal opacity) { /** * Scales opacity from the range 0...100 * to the integer range 0...255 */ return qMin(255, int(opacity * 2.55 + 0.5)); } void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity, bool finalChange) { if (!node) return; if (node->opacity() == opacity) return; if (!finalChange) { node->setOpacity(opacity); node->setDirty(); } else { m_d->commandsAdapter.setOpacity(node, opacity); } } void KisNodeManager::setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp) { if (!node) return; if (node->compositeOp() == compositeOp) return; m_d->commandsAdapter.setCompositeOp(node, compositeOp); } void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes) { if (activeNode) { slotNonUiActivatedNode(activeNode); } if (!selectedNodes.isEmpty()) { slotSetSelectedNodes(selectedNodes); } } void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes) { m_d->selectedNodes = nodes; emit sigUiNeedChangeSelectedNodes(nodes); } KisNodeList KisNodeManager::selectedNodes() { return m_d->selectedNodes; } KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const { return m_d->nodeSelectionAdapter.data(); } KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const { return m_d->nodeInsertionAdapter.data(); } void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange) { KisNodeSP node = activeNode(); setNodeOpacity(node, convertOpacityToInt(opacity), finalChange); } void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op) { KisNodeSP node = activeNode(); setNodeCompositeOp(node, op); } void KisNodeManager::duplicateActiveNode() { KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->duplicateNode(selectedNodes()); } KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName) { KisImageWSP image = view->image(); if (!nodeJuggler || (nodeJuggler && !nodeJuggler->canMergeAction(actionName))) { nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750); nodeJuggler->setAutoDelete(true); } return nodeJuggler; } void KisNodeManager::raiseNode() { KUndo2MagicString actionName = kundo2_i18n("Raise Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->raiseNode(selectedNodes()); } void KisNodeManager::lowerNode() { KUndo2MagicString actionName = kundo2_i18n("Lower Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->lowerNode(selectedNodes()); } void KisNodeManager::removeSingleNode(KisNodeSP node) { if (!node || !node->parent()) { return; } KisNodeList nodes; nodes << node; removeSelectedNodes(nodes); } void KisNodeManager::removeSelectedNodes(KisNodeList nodes) { KUndo2MagicString actionName = kundo2_i18n("Remove Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::removeNode() { removeSelectedNodes(selectedNodes()); } void KisNodeManager::mirrorNodeX() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer X"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask X"); } mirrorNode(node, commandName, Qt::Horizontal); } void KisNodeManager::mirrorNodeY() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer Y"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask Y"); } mirrorNode(node, commandName, Qt::Vertical); } inline bool checkForGlobalSelection(KisNodeSP node) { return dynamic_cast(node.data()) && node->parent() && !node->parent()->parent(); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); while (node && node->childCount() > 0) { node = node->firstChild(); } if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } while(node && checkForGlobalSelection(node)) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node; if (activeNode->childCount() > 0) { node = activeNode->lastChild(); } else { node = activeNode->prevSibling(); } while (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); activeNode = activeNode->parent(); } while(node && checkForGlobalSelection(node)) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::switchToPreviouslyActiveNode() { if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) { slotNonUiActivatedNode(m_d->previouslyActiveNode); } } void KisNodeManager::rotate(double radians) { if(!m_d->view->image()) return; KisNodeSP node = activeNode(); if (!node) return; if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return; m_d->view->image()->rotateNode(node, radians); } void KisNodeManager::rotate180() { rotate(M_PI); } void KisNodeManager::rotateLeft90() { rotate(-M_PI / 2); } void KisNodeManager::rotateRight90() { rotate(M_PI / 2); } void KisNodeManager::shear(double angleX, double angleY) { if (!m_d->view->image()) return; KisNodeSP node = activeNode(); if (!node) return; if(!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return; m_d->view->image()->shearNode(node, angleX, angleY); } void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy) { KisNodeSP node = activeNode(); KIS_ASSERT_RECOVER_RETURN(node); m_d->view->image()->scaleNode(node, sx, sy, filterStrategy); nodesUpdated(); } void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(m_d->view->image(), node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); nodesUpdated(); } void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity) { KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18n("Export \"%1\"", defaultName)); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export)); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);; QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); doc->setOutputMimeType(mimefilter.toLatin1()); doc->exportDocument(url); } void KisNodeManager::saveNodeAsImage() { KisNodeSP node = activeNode(); if (!node) { warnKrita << "BUG: Save Node As Image was called without any node selected"; return; } KisImageWSP image = m_d->view->image(); QRect saveRect = image->bounds() | node->exactBounds(); KisPaintDeviceSP device = node->paintDevice(); if (!device) { device = node->projection(); } m_d->saveDeviceAsImage(device, node->name(), saveRect, image->xRes(), image->yRes(), node->opacity()); } void KisNodeManager::slotSplitAlphaIntoMask() { KisNodeSP node = activeNode(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice()); KisPaintDeviceSP srcDevice = node->paintDevice(); const KoColorSpace *srcCS = srcDevice->colorSpace(); const QRect processRect = srcDevice->exactBounds() | srcDevice->defaultBounds()->bounds(); KisPaintDeviceSP selectionDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask")); KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice); KisSequentialIterator srcIt(srcDevice, processRect); KisSequentialIterator dstIt(selectionDevice, processRect); do { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } while (srcIt.nextPixel() && dstIt.nextPixel()); m_d->commandsAdapter.addExtraCommand(transaction.endAndTake()); createNode("KisTransparencyMask", false, selectionDevice); m_d->commandsAdapter.endMacro(); } void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers) { KisNodeSP node = q->activeNode(); KisNodeSP parentNode = node->parent(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask")); if (writeToLayers && !parentNode->hasEditablePaintDevice()) { QMessageBox::information(view->mainWindow(), i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.").arg(parentNode->name())); return; } KisPaintDeviceSP dstDevice; if (writeToLayers) { KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice()); dstDevice = parentNode->paintDevice(); } else { KisPaintDeviceSP copyDevice = parentNode->paintDevice(); if (!copyDevice) { copyDevice = parentNode->original(); } dstDevice = new KisPaintDevice(*copyDevice); } const KoColorSpace *dstCS = dstDevice->colorSpace(); KisPaintDeviceSP selectionDevice = node->paintDevice(); KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1); const QRect processRect = selectionDevice->exactBounds() | dstDevice->exactBounds() | selectionDevice->defaultBounds()->bounds(); QScopedPointer transaction; if (writeToLayers) { commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer")); transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice)); } KisSequentialIterator srcIt(selectionDevice, processRect); KisSequentialIterator dstIt(dstDevice, processRect); do { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } while (srcIt.nextPixel() && dstIt.nextPixel()); if (writeToLayers) { commandsAdapter.addExtraCommand(transaction->endAndTake()); commandsAdapter.removeNode(node); commandsAdapter.endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } +void KisNodeManager::toggleLock() +{ + KisNodeList nodes = this->selectedNodes(); + KisNodeSP active = activeNode(); + if (nodes.isEmpty() || !active) return; + + bool isLocked = active->userLocked(); + + for (auto &node : nodes) { + node->setUserLocked(!isLocked); + } +} + +void KisNodeManager::toggleVisibility() +{ + KisNodeList nodes = this->selectedNodes(); + KisNodeSP active = activeNode(); + if (nodes.isEmpty() || !active) return; + + bool isVisible = active->visible(); + + for (auto &node : nodes) { + node->setVisible(!isVisible); + node->setDirty(); + } +} + +void KisNodeManager::toggleAlphaLock() +{ + KisNodeList nodes = this->selectedNodes(); + KisNodeSP active = activeNode(); + if (nodes.isEmpty() || !active) return; + + auto layer = qobject_cast(active.data()); + if (!layer) { + return; + } + + bool isAlphaLocked = layer->alphaLocked(); + for (auto &node : nodes) { + auto layer = qobject_cast(node.data()); + if (layer) { + layer->setAlphaLocked(!isAlphaLocked); + } + } +} + +void KisNodeManager::toggleInheritAlpha() +{ + KisNodeList nodes = this->selectedNodes(); + KisNodeSP active = activeNode(); + if (nodes.isEmpty() || !active) return; + + auto layer = qobject_cast(active.data()); + if (!layer) { + return; + } + + bool isAlphaDisabled = layer->alphaChannelDisabled(); + for (auto &node : nodes) { + auto layer = qobject_cast(node.data()); + if (layer) { + layer->disableAlphaChannel(!isAlphaDisabled); + node->setDirty(); + } + } +} + void KisNodeManager::cutLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); if (nodes.isEmpty()) return; KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false); KUndo2MagicString actionName = kundo2_i18n("Cut Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::copyLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true); } void KisNodeManager::pasteLayersFromClipboard() { const QMimeData *data = KisClipboard::instance()->layersMimeData(); if (!data) return; KisNodeSP activeNode = this->activeNode(); KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(dummiesFacade); const bool copyNode = false; KisImageSP image = m_d->view->image(); KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode); KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0; KisMimeData::insertMimeLayers(data, image, shapeController, parentDummy, aboveThisDummy, copyNode, nodeInsertionAdapter()); } void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild) { KisNodeSP active = activeNode(); if (!active) return; KisImageSP image = m_d->view->image(); QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName(); KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8); KisNodeList nodes1; nodes1 << group; KisNodeList nodes2; nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes()); KisLayerUtils::filterMergableNodes(nodes2); if (KisLayerUtils::checkIsChildOf(active, nodes2)) { active = nodes2.first(); } KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; juggler->addNode(nodes1, parent, aboveThis); juggler->moveNode(nodes2, group, 0); *newGroup = group; *newLastChild = nodes2.last(); } void KisNodeManager::createQuickGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; createQuickGroupImpl(juggler, "", &parent, &above); } void KisNodeManager::createQuickClippingGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; KisImageSP image = m_d->view->image(); createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above); KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace()); maskLayer->disableAlphaChannel(true); juggler->addNode(KisNodeList() << maskLayer, parent, above); } void KisNodeManager::quickUngroup() { KisNodeSP active = activeNode(); if (!active) return; KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup"); if (parent && dynamic_cast(active.data())) { KisNodeList nodes = active->childNodes(QStringList(), KoProperties()); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, active); juggler->removeNode(KisNodeList() << active); } else if (parent && parent->parent()) { KisNodeSP grandParent = parent->parent(); KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties()); KisNodeList allSelectedNodes = selectedNodes(); const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(allSelectedNodes, grandParent, parent); if (removeParent) { juggler->removeNode(KisNodeList() << parent); } } } void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps) { KisImageSP image = m_d->view->image(); KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true); KisNodeList selectedNodes = this->selectedNodes(); if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) { nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true); } if (!nodes.isEmpty()) { slotImageRequestNodeReselection(nodes.last(), nodes); } } void KisNodeManager::selectAllNodes() { KoProperties props; selectLayersImpl(props, props); } void KisNodeManager::selectVisibleNodes() { KoProperties props; props.setProperty("visible", true); KoProperties invertedProps; invertedProps.setProperty("visible", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectLockedNodes() { KoProperties props; props.setProperty("locked", true); KoProperties invertedProps; invertedProps.setProperty("locked", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectInvisibleNodes() { KoProperties props; props.setProperty("visible", false); KoProperties invertedProps; invertedProps.setProperty("visible", true); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectUnlockedNodes() { KoProperties props; props.setProperty("locked", false); KoProperties invertedProps; invertedProps.setProperty("locked", true); selectLayersImpl(props, invertedProps); } diff --git a/libs/ui/kis_node_manager.h b/libs/ui/kis_node_manager.h index 6f22ce1e26..80dc92a9d2 100644 --- a/libs/ui/kis_node_manager.h +++ b/libs/ui/kis_node_manager.h @@ -1,253 +1,258 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_NODE_MANAGER #define KIS_NODE_MANAGER #include #include #include "kis_types.h" #include class KActionCollection; class KoCompositeOp; class KoColorSpace; class KUndo2MagicString; class KisFilterStrategy; class KisViewManager; class KisActionManager; class KisView; class KisNodeSelectionAdapter; class KisNodeInsertionAdapter; class KisNodeJugglerCompressed; class KoProperties; /** * The node manager passes requests for new layers or masks on to the mask and layer * managers. */ class KRITAUI_EXPORT KisNodeManager : public QObject { Q_OBJECT public: KisNodeManager(KisViewManager * view); ~KisNodeManager() override; void setView(QPointerimageView); Q_SIGNALS: /// emitted whenever a node is selected. void sigNodeActivated(KisNodeSP node); /// emitted whenever a different layer is selected. void sigLayerActivated(KisLayerSP layer); /// for the layer box: this sets the current node in the layerbox /// without telling the node manager that the node is activated, /// preventing loops (I think...) void sigUiNeedChangeActiveNode(KisNodeSP node); void sigUiNeedChangeSelectedNodes(const KisNodeList &nodes); public: void setup(KActionCollection * collection, KisActionManager* actionManager); void updateGUI(); /// Convenience function to get the active layer or mask KisNodeSP activeNode(); /// convenience function to get the active layer. If a mask is /// active, it's parent layer is the active layer. KisLayerSP activeLayer(); /// Get the paint device the user wants to paint on now KisPaintDeviceSP activePaintDevice(); /** * @return the active color space used for composition, meaning the color space * of the active mask, or the color space of the parent of the active layer */ const KoColorSpace* activeColorSpace(); /** * Sets opacity for the node in a universal way (masks/layers) */ void setNodeOpacity(KisNodeSP node, qint32 opacity, bool finalChange); /** * Sets compositeOp for the node in a universal way (masks/layers) */ void setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp); KisNodeList selectedNodes(); KisNodeSelectionAdapter* nodeSelectionAdapter() const; KisNodeInsertionAdapter* nodeInsertionAdapter() const; public Q_SLOTS: /** * Explicitly activates \p node * The UI will be noticed that active node has been changed. * Both sigNodeActivated and sigUiNeedChangeActiveNode are emitted. * * WARNING: normally you needn't call this method manually. It is * automatically called when a node is added to the graph. If you * have some special cases when you need to activate a node, consider * adding them to KisDummiesFacadeBase instead. Calling this method * directly should be the last resort. * * \see slotUiActivatedNode for comparison */ void slotNonUiActivatedNode(KisNodeSP node); /** * Activates \p node. * All non-ui listeners are notified with sigNodeActivated, * sigUiNeedChangeActiveNode is *not* emitted. * * \see activateNode */ void slotUiActivatedNode(KisNodeSP node); /** * Adds a list of nodes without searching appropriate position for * it. You *must* ensure that the nodes are allowed to be added * to the parent, otherwise you'll get an assert. */ void addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Moves a list of nodes without searching appropriate position * for it. You *must* ensure that the nodes are allowed to be * added to the parent, otherwise you'll get an assert. */ void moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Copies a list of nodes without searching appropriate position * for it. You *must* ensure that the nodes are allowed to be * added to the parent, otherwise you'll get an assert. */ void copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Create new layer from actually visible */ void createFromVisible(); void toggleIsolateActiveNode(); void toggleIsolateMode(bool checked); void slotUpdateIsolateModeAction(); void slotTryFinishIsolatedMode(); void moveNodeAt(KisNodeSP node, KisNodeSP parent, int index); void createNode(const QString& nodeType, bool quiet = false, KisPaintDeviceSP copyFrom = 0); void convertNode(const QString &nodeType); void nodesUpdated(); void nodeProperties(KisNodeSP node); void nodeOpacityChanged(qreal opacity, bool finalChange); void nodeCompositeOpChanged(const KoCompositeOp* op); void duplicateActiveNode(); void removeNode(); void mirrorNodeX(); void mirrorNodeY(); void mirrorNode(KisNodeSP node, const KUndo2MagicString& commandName, Qt::Orientation orientation); void activateNextNode(); void activatePreviousNode(); void switchToPreviouslyActiveNode(); /** * move the active node up the nodestack. */ void raiseNode(); /** * move the active node down the nodestack */ void lowerNode(); void rotate(double radians); void rotate180(); void rotateLeft90(); void rotateRight90(); void saveNodeAsImage(); void slotSplitAlphaIntoMask(); void slotSplitAlphaWrite(); void slotSplitAlphaSaveMerged(); + void toggleLock(); + void toggleVisibility(); + void toggleAlphaLock(); + void toggleInheritAlpha(); + /** * @brief slotSetSelectedNodes set the list of nodes selected in the layerbox. Selected nodes are not necessarily active nodes. * @param nodes the selected nodes */ void slotSetSelectedNodes(const KisNodeList &nodes); void slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); void cutLayersToClipboard(); void copyLayersToClipboard(); void pasteLayersFromClipboard(); void createQuickGroup(); void createQuickClippingGroup(); void quickUngroup(); void selectAllNodes(); void selectVisibleNodes(); void selectLockedNodes(); void selectInvisibleNodes(); void selectUnlockedNodes(); public: void shear(double angleX, double angleY); void scale(double sx, double sy, KisFilterStrategy *filterStrategy); void removeSingleNode(KisNodeSP node); KisLayerSP createPaintLayer(); private: /** * Scales opacity from the range 0...1 * to the integer range 0...255 */ qint32 convertOpacityToInt(qreal opacity); void removeSelectedNodes(KisNodeList selectedNodes); void slotSomethingActivatedNodeImpl(KisNodeSP node); void createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild); void selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps); struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index 5e3c69954c..7981cec326 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1324 +1,1324 @@ /* * 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 "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")); 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(); KisAction* hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); hideCanvasDecorationsX->setCheckable(true); hideCanvasDecorationsX->setText(i18n("Hide Mirror Line")); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); KisAction* lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); lockActionX->setText(i18n("Lock")); lockActionX->setCheckable(true); toolbarMenuXMirror->addAction(lockActionX); KisAction* moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); moveToCenterActionX->setCheckable(false); moveToCenterActionX->setText(i18n("Move to Canvas Center")); toolbarMenuXMirror->addAction(moveToCenterActionX); // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); KisAction* hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); hideCanvasDecorationsY->setCheckable(true); hideCanvasDecorationsY->setText(i18n("Hide Mirror Line")); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); KisAction* lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); lockActionY->setText(i18n("Lock")); lockActionY->setCheckable(true); toolbarMenuYMirror->addAction(lockActionY); KisAction* moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); moveToCenterActionY->setCheckable(false); moveToCenterActionY->setText(i18n("Move to Canvas Center")); 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_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider); 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_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(savePresetClicked()) , SLOT(slotSaveActivePreset())); 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())); m_favoriteResourceManager = new KisFavoriteResourceManager(this); 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) { 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::slotSaveActivePreset() { KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset(); if (!curPreset) return; m_favoriteResourceManager->setBlockUpdates(true); KisPaintOpPresetSP newPreset = curPreset->clone(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QString saveLocation = rServer->saveLocation(); QString presetName = m_presetsPopup->getPresetName(); QString presetFilename = saveLocation + presetName + newPreset->defaultFileExtension(); QStringList tags; KisPaintOpPresetSP resource = rServer->resourceByName(presetName); if (resource) { tags = rServer->assignedTagsList(resource.data()); rServer->removeResourceAndBlacklist(resource); } newPreset->setImage(m_presetsPopup->cutOutOverlay()); newPreset->setFilename(presetFilename); newPreset->setName(presetName); newPreset->setPresetDirty(false); rServer->addResource(newPreset); Q_FOREACH (const QString & tag, tags) { rServer->addTag(newPreset.data(), tag); } // HACK ALERT! the server does not notify the observers // automatically, so we need to call theupdate manually! rServer->tagCategoryMembersChanged(); restoreResource(newPreset.data()); m_favoriteResourceManager->setBlockUpdates(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); } 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 | ENABLE_SIZE | ENABLE_FLOW); + setWidgetState(ENABLE_PRESETS); slotUpdatePreset(); m_presetsEnabled = true; } else { - setWidgetState(DISABLE_PRESETS | DISABLE_SIZE | DISABLE_FLOW); + 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; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == preset->name()) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } return; } i++; } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset()->name() == preset->name()) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } return; } i++; } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { //qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset(); setCurrentPaintop(m_resourceProvider->previousPreset()); } } 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_png_converter.cpp b/libs/ui/kis_png_converter.cpp index ad18ad6a78..cca0451b49 100644 --- a/libs/ui/kis_png_converter.cpp +++ b/libs/ui/kis_png_converter.cpp @@ -1,1287 +1,1276 @@ /* * Copyright (c) 2005-2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_png_converter.h" // A big thank to Glenn Randers-Pehrson for his wonderful // documentation of libpng available at // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html #ifndef PNG_MAX_UINT // Removed in libpng 1.4 #define PNG_MAX_UINT PNG_UINT_31_MAX #endif #include // WORDS_BIGENDIAN #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 "dialogs/kis_dlg_png_import.h" #include "kis_clipboard.h" namespace { int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha) { QString id = cs->id(); if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") { return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY; } if (id == "RGBA" || id == "RGBA16") { return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; } return -1; } bool colorSpaceIdSupported(const QString &id) { return id == "RGBA" || id == "RGBA16" || id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16"; } QPair getColorSpaceForColorType(int color_type, int color_nb_bits) { QPair r; if (color_type == PNG_COLOR_TYPE_PALETTE) { r.first = RGBAColorModelID.id(); r.second = Integer8BitsColorDepthID.id(); } else { if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { r.first = GrayAColorModelID.id(); } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) { r.first = RGBAColorModelID.id(); } if (color_nb_bits == 16) { r.second = Integer16BitsColorDepthID.id(); } else if (color_nb_bits <= 8) { r.second = Integer8BitsColorDepthID.id(); } } return r; } void fillText(png_text* p_text, const char* key, QString& text) { p_text->compression = PNG_TEXT_COMPRESSION_zTXt; p_text->key = const_cast(key); char* textc = new char[text.length()+1]; strcpy(textc, text.toLatin1()); p_text->text = textc; p_text->text_length = text.length() + 1; } long formatStringList(char *string, const size_t length, const char *format, va_list operands) { int n = vsnprintf(string, length, format, operands); if (n < 0) string[length-1] = '\0'; return((long) n); } long formatString(char *string, const size_t length, const char *format, ...) { long n; va_list operands; va_start(operands, format); n = (long) formatStringList(string, length, format, operands); va_end(operands); return(n); } void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data) { png_textp text; png_uint_32 allocated_length, description_length; const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl; text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); description_length = profile_type.length(); allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length); text[0].text = (png_charp) png_malloc(ping, allocated_length); QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1(); QByteArray keyData = key.toLatin1(); text[0].key = keyData.data(); uchar* sp = (uchar*)profile_data.data(); png_charp dp = text[0].text; *dp++ = '\n'; memcpy(dp, profile_type.toLatin1().constData(), profile_type.length()); dp += description_length; *dp++ = '\n'; formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length()); dp += 8; for (long i = 0; i < (long) profile_data.length(); i++) { if (i % 36 == 0) *dp++ = '\n'; *(dp++) = (char) hex[((*sp >> 4) & 0x0f)]; *(dp++) = (char) hex[((*sp++) & 0x0f)]; } *dp++ = '\n'; *dp = '\0'; text[0].text_length = (png_size_t)(dp - text[0].text); text[0].compression = -1; if (text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1); png_free(ping, text[0].text); png_free(ping, text); } QByteArray png_read_raw_profile(png_textp text) { QByteArray profile; static const unsigned char unhex[103] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 }; png_charp sp = text[0].text + 1; /* look for newline */ while (*sp != '\n') sp++; /* look for length */ while (*sp == '\0' || *sp == ' ' || *sp == '\n') sp++; png_uint_32 length = (png_uint_32) atol(sp); while (*sp != ' ' && *sp != '\n') sp++; if (length == 0) { return profile; } profile.resize(length); /* copy profile, skipping white space and column 1 "=" signs */ unsigned char *dp = (unsigned char*)profile.data(); png_uint_32 nibbles = length * 2; for (png_uint_32 i = 0; i < nibbles; i++) { while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') { if (*sp == '\0') { return QByteArray(); } sp++; } if (i % 2 == 0) *dp = (unsigned char)(16 * unhex[(int) *sp++]); else (*dp++) += unhex[(int) *sp++]; } return profile; } void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize) { dbgFile << "Decoding " << type << " " << text[0].key; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type); Q_ASSERT(exifIO); QByteArray rawProfile = png_read_raw_profile(text); if (headerSize > 0) { rawProfile.remove(0, headerSize); } if (rawProfile.size() > 0) { QBuffer buffer; buffer.setData(rawProfile); exifIO->loadFrom(store, &buffer); } else { dbgFile << "Decoding failed"; } } } KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode) { // Q_ASSERT(doc); // Q_ASSERT(adapter); m_doc = doc; m_stop = false; m_max_row = 0; m_image = 0; m_batchMode = batchMode; } KisPNGConverter::~KisPNGConverter() { } class KisPNGReadStream { public: KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { } int nextValue() { if (m_posinc == 0) { m_posinc = 8; m_buf++; } m_posinc -= m_depth; return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1)); } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGWriteStream { public: KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { *m_buf = 0; } void setNextValue(int v) { if (m_posinc == 0) { m_posinc = 8; m_buf++; *m_buf = 0; } m_posinc -= m_depth; *m_buf = (v << m_posinc) | *m_buf; } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGReaderAbstract { public: KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {} virtual ~KisPNGReaderAbstract() {} virtual png_bytep readLine() = 0; protected: png_structp png_ptr; int width, height; }; class KisPNGReaderLineByLine : public KisPNGReaderAbstract { public: KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) { png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); row_pointer = new png_byte[rowbytes]; } ~KisPNGReaderLineByLine() override { delete[] row_pointer; } png_bytep readLine() override { png_read_row(png_ptr, row_pointer, 0); return row_pointer; } private: png_bytep row_pointer; }; class KisPNGReaderFullImage : public KisPNGReaderAbstract { public: KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) { row_pointers = new png_bytep[height]; png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); for (int i = 0; i < height; i++) { row_pointers[i] = new png_byte[rowbytes]; } png_read_image(png_ptr, row_pointers); } ~KisPNGReaderFullImage() override { for (int i = 0; i < height; i++) { delete[] row_pointers[i]; } delete[] row_pointers; } png_bytep readLine() override { return row_pointers[y++]; } private: png_bytepp row_pointers; int y; }; static void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr); while (length) { int nr = in->read((char*)data, length); if (nr <= 0) { png_error(png_ptr, "Read Error"); return; } length -= nr; } } static void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr); uint nr = out->write((char*)data, length); if (nr != length) { png_error(png_ptr, "Write Error"); return; } } static void _flush_fn(png_structp png_ptr) { Q_UNUSED(png_ptr); } KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod) { dbgFile << "Start decoding PNG File"; png_byte signature[8]; iod->peek((char*)signature, 8); #if PNG_LIBPNG_VER < 10400 if (!png_check_sig(signature, 8)) { #else if (png_sig_cmp(signature, 0, 8) != 0) { #endif iod->close(); return (KisImageBuilder_RESULT_BAD_FETCH); } // Initialize the internal structures png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { iod->close(); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } // Catch errors if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } // Initialize the special png_set_read_fn(png_ptr, iod, _read_fn); #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif // read all PNG info up to image data png_read_info(png_ptr, info_ptr); if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_expand(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_packing(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE && (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) { png_set_expand(png_ptr); } png_read_update_info(png_ptr, info_ptr); // Read information about the png png_uint_32 width, height; int color_nb_bits, color_type, interlace_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0); dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl; // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Determine the colorspace QPair csName = getColorSpaceForColorType(color_type, color_nb_bits); if (csName.first.isEmpty()) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA); // Read image profile png_charp profile_name; #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_bytep profile_data; #else png_charp profile_data; #endif int compression_type; png_uint_32 proflen; // Get the various optional chunks // https://www.w3.org/TR/PNG/#11cHRM #if defined(PNG_cHRM_SUPPORTED) double whitePointX, whitePointY; double redX, redY; double greenX, greenY; double blueX, blueY; png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY); qDebug() << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY; #endif // https://www.w3.org/TR/PNG/#11gAMA #if defined(PNG_GAMMA_SUPPORTED) double gamma; png_get_gAMA(png_ptr, info_ptr, &gamma); qDebug() << "gAMA" << gamma; #endif // https://www.w3.org/TR/PNG/#11sRGB #if defined(PNG_sRGB_SUPPORTED) int sRGBIntent; png_get_sRGB(png_ptr, info_ptr, &sRGBIntent); qDebug() << "sRGB" << sRGBIntent; #endif bool fromBlender = false; png_text* text_ptr; int num_comments; png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); if (key == "file") { QString relatedFile = text_ptr[i].text; if (relatedFile.contains(".blend", Qt::CaseInsensitive)){ fromBlender=true; } } } const KoColorProfile* profile = 0; if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) { QByteArray profile_rawdata; // XXX: Hardcoded for icc type -- is that correct for us? profile_rawdata.resize(proflen); memcpy(profile_rawdata.data(), profile_data, proflen); profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata); Q_CHECK_PTR(profile); if (profile) { // dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } } else { dbgFile << "no embedded profile, will use the default profile"; if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) { KisConfig cfg; quint32 behaviour = cfg.pasteBehaviour(); if (behaviour == PASTE_ASK) { KisDlgPngImport dlg(m_path, csName.first, csName.second); QApplication::restoreOverrideCursor(); dlg.exec(); if (!dlg.profile().isEmpty()) { - - QString s = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second); - - const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s); - if (csf) { - QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf); - Q_FOREACH (const KoColorProfile *p, profileList) { - if (p->name() == dlg.profile()) { - profile = p; - break; - } - } - } + profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile()); } QApplication::setOverrideCursor(Qt::WaitCursor); } } dbgFile << "no embedded profile, will use the default profile"; } + const QString colorSpaceId = + KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second); + // Check that the profile is used by the color space - if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory( - KoColorSpaceRegistry::instance()->colorSpaceId( - csName.first, csName.second))->profileIsCompatible(profile)) { + if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile: " << profile->name() << "\n"; cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile); } else { if (csName.first == RGBAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc"); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0); } } if (cs == 0) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } //TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Creating the KisImageSP if (m_image == 0) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); Q_CHECK_PTR(m_image); } // Read resolution int unit_type; png_uint_32 x_resolution, y_resolution; png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type); if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) { m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points } double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1); KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX); // Read comments/texts... png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); if (m_doc) { KoDocumentInfo * info = m_doc->documentInfo(); dbgFile << "There are " << num_comments << " comments in the text"; for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); dbgFile << "key is |" << text_ptr[i].key << "| containing " << text_ptr[i].text << " " << (key == "Raw profile type exif "); if (key == "title") { info->setAboutInfo("title", text_ptr[i].text); } else if (key == "description") { info->setAboutInfo("comment", text_ptr[i].text); } else if (key == "author") { qDebug()<<"Author:"<setAuthorInfo("creator", text_ptr[i].text); } else if (key.contains("Raw profile type exif")) { decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6); } else if (key.contains("Raw profile type iptc")) { decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14); } else if (key.contains("Raw profile type xmp")) { decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0); } else if (key == "version") { m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text))); } else if (key == "preset") { m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text))); } } } // Read image data KisPNGReaderAbstract* reader = 0; try { if (interlace_type == PNG_INTERLACE_ADAM7) { reader = new KisPNGReaderFullImage(png_ptr, info_ptr, width, height); } else { reader = new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height); } } catch (std::bad_alloc& e) { // new png_byte[] may raise such an exception if the image // is invalid / to large. dbgFile << "bad alloc: " << e.what(); // Free only the already allocated png_byte instances. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return (KisImageBuilder_RESULT_FAILURE); } // Read the palette if the file is indexed png_colorp palette ; int num_palette; if (color_type == PNG_COLOR_TYPE_PALETTE) { png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); } // Read the transparency palette quint8 palette_alpha[256]; memset(palette_alpha, 255, 256); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { if (color_type == PNG_COLOR_TYPE_PALETTE) { png_bytep alpha_ptr; int num_alpha; png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0); for (int i = 0; i < num_alpha; ++i) { palette_alpha[i] = alpha_ptr[i]; } } } for (png_uint_32 y = 0; y < height; y++) { KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width); png_bytep row_pointer = reader->readLine(); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[0] = *(src++); if (transform) transform->transform(reinterpret_cast(d), reinterpret_cast(d), 1); if (hasalpha) { d[1] = *(src++); } else { d[1] = quint16_MAX; } } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[0] = (quint8)(stream.nextValue() * coeff); if (transform) transform->transform(d, d, 1); if (hasalpha) { d[1] = (quint8)(stream.nextValue() * coeff); } else { d[1] = UCHAR_MAX; } } while (it->nextPixel()); } // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits" break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(reinterpret_cast(d), reinterpret_cast(d), 1); if (hasalpha) d[3] = *(src++); else d[3] = quint16_MAX; } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[2] = (quint8)(stream.nextValue() * coeff); d[1] = (quint8)(stream.nextValue() * coeff); d[0] = (quint8)(stream.nextValue() * coeff); if (transform) transform->transform(d, d, 1); if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff); else d[3] = UCHAR_MAX; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); quint8 index = stream.nextValue(); quint8 alpha = palette_alpha[ index ]; if (alpha == 0) { memset(d, 0, 4); } else { png_color c = palette[ index ]; d[2] = c.red; d[1] = c.green; d[0] = c.blue; d[3] = alpha; } } while (it->nextPixel()); } break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_image->addNode(layer.data(), m_image->rootLayer().data()); png_read_end(png_ptr, end_info); iod->close(); // Freeing memory png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); delete reader; return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename) { m_path = filename; QFile fp(filename); if (fp.exists()) { if (!fp.open(QIODevice::ReadOnly)) { dbgFile << "Failed to open PNG File"; return (KisImageBuilder_RESULT_FAILURE); } return buildImage(&fp); } return (KisImageBuilder_RESULT_NOT_EXIST); } KisImageSP KisPNGConverter::image() { return m_image; } bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData) { if (store->open(filename)) { KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { dbgFile << "Could not open for writing:" << filename; return false; } KisPNGConverter pngconv(0); vKisAnnotationSP_it annotIt = 0; KisMetaData::Store* metaDataStore = 0; if (metaData) { metaDataStore = new KisMetaData::Store(*metaData); } KisPNGOptions options; options.compression = 0; options.interlace = false; options.tryToSaveAsIndexed = false; options.alpha = true; options.saveSRGBProfile = false; if (dev->colorSpace()->id() != "RGBA") { dev = new KisPaintDevice(*dev.data()); KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); delete cmd; } bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore); if (success != KisImageBuilder_RESULT_OK) { dbgFile << "Saving PNG failed:" << filename; delete metaDataStore; return false; } delete metaDataStore; io.close(); if (!store->close()) { return false; } } else { dbgFile << "Opening of data file failed :" << filename; return false; } return true; } KisImageBuilder_Result KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { dbgFile << "Start writing PNG File " << filename; // Open a QIODevice for writing QFile fp (filename); if (!fp.open(QIODevice::WriteOnly)) { dbgFile << "Failed to open PNG File for writing"; return (KisImageBuilder_RESULT_FAILURE); } KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData); return result; } KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { if (!device) return KisImageBuilder_RESULT_INVALID_ARG; if (!options.alpha) { KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); KoColor c(options.transparencyFillColor, device->colorSpace()); tmp->fill(imageRect, c); KisPainter gc(tmp); gc.bitBlt(imageRect.topLeft(), device, imageRect); gc.end(); device = tmp; } if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID || device->colorSpace()->colorDepthId() == Float32BitsColorDepthID || device->colorSpace()->colorDepthId() == Float64BitsColorDepthID) { const KoColorSpace *dstcs = KoColorSpaceRegistry::instance()->colorSpace(device->colorSpace()->colorModelId().id(), Integer8BitsColorDepthID.id(), ""); KisPaintDeviceSP tmp = new KisPaintDevice(dstcs); KisPainter gc(tmp); gc.bitBlt(imageRect.topLeft(), device, imageRect); gc.end(); device = tmp; } if (options.forceSRGB) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); device = new KisPaintDevice(*device); KUndo2Command *cmd = device->convertTo(cs); delete cmd; } // Initialize structures png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { return (KisImageBuilder_RESULT_FAILURE); } #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED png_set_check_for_invalid_index(png_ptr, 0); #endif png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)0); return (KisImageBuilder_RESULT_FAILURE); } // If an error occurs during writing, libpng will jump here if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return (KisImageBuilder_RESULT_FAILURE); } // Initialize the writing // png_init_io(png_ptr, fp); // Setup the progress function // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);" // setProgressTotalSteps(100/*height*/); /* set the zlib compression level */ png_set_compression_level(png_ptr, options.compression); png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn); /* set other zlib parameters */ png_set_compression_mem_level(png_ptr, 8); png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); png_set_compression_window_bits(png_ptr, 15); png_set_compression_method(png_ptr, 8); png_set_compression_buffer_size(png_ptr, 8192); int color_nb_bits = 8 * device->pixelSize() / device->channelCount(); int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha); Q_ASSERT(color_type > -1); // Try to compute a table of color if the colorspace is RGB8f png_colorp palette = 0; int num_palette = 0; if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8 palette = new png_color[255]; KisSequentialIterator it(device, imageRect); bool toomuchcolor = false; do { const quint8* c = it.oldRawData(); bool findit = false; for (int i = 0; i < num_palette; i++) { if (palette[i].red == c[2] && palette[i].green == c[1] && palette[i].blue == c[0]) { findit = true; break; } } if (!findit) { if (num_palette == 255) { toomuchcolor = true; break; } palette[num_palette].red = c[2]; palette[num_palette].green = c[1]; palette[num_palette].blue = c[0]; num_palette++; } } while (it.nextPixel()); if (!toomuchcolor) { dbgFile << "Found a palette of " << num_palette << " colors"; color_type = PNG_COLOR_TYPE_PALETTE; if (num_palette <= 2) { color_nb_bits = 1; } else if (num_palette <= 4) { color_nb_bits = 2; } else if (num_palette <= 16) { color_nb_bits = 4; } else { color_nb_bits = 8; } } else { delete [] palette; } } int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; png_set_IHDR(png_ptr, info_ptr, imageRect.width(), imageRect.height(), color_nb_bits, color_type, interlacetype, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); /* * This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's * color management is bugged, so once you give it any incentive to start color managing an sRGB image it * will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now. */ /*if (!options.saveSRGBProfile && sRGB) { png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); }*/ // set the palette if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_PLTE(png_ptr, info_ptr, palette, num_palette); } // Save annotation vKisAnnotationSP_it it = annotationsStart; while (it != annotationsEnd) { if (!(*it) || (*it)->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size(); if ((*it) -> type().startsWith(QString("krita_attribute:"))) { // // Attribute // XXX: it should be possible to save krita_attributes in the \"CHUNKs\"" dbgFile << "cannot save this annotation : " << (*it) -> type(); } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) { dbgFile << "Saving preset information " << (*it)->description(); png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text)); QByteArray keyData = (*it)->description().toLatin1(); text[0].key = keyData.data(); text[0].text = (char*)(*it)->annotation().data(); text[0].text_length = (*it)->annotation().size(); text[0].compression = -1; png_set_text(png_ptr, info_ptr, text, 1); png_free(png_ptr, text); } it++; } // Save the color profile const KoColorProfile* colorProfile = device->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); if (!sRGB || options.saveSRGBProfile) { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (const png_bytep)colorProfileData.constData(), colorProfileData . size()); #else png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (char*)colorProfileData.constData(), colorProfileData . size()); #endif } // read comments from the document information // warning: according to the official png spec, the keys need to be capitalised! if (m_doc) { png_text texts[3]; int nbtexts = 0; KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty()) { fillText(texts + nbtexts, "Title", title); nbtexts++; } QString abstract = info->aboutInfo("comment"); if (!abstract.isEmpty()) { fillText(texts + nbtexts, "Description", abstract); nbtexts++; } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { fillText(texts + nbtexts, "Author", author); nbtexts++; } png_set_text(png_ptr, info_ptr, texts, nbtexts); } // Save metadata following imagemagick way // Save exif if (metaData && !metaData->empty()) { if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); writeRawProfile(png_ptr, info_ptr, "exif", buffer.data()); } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data()); } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader); dbgFile << "XMP information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data()); } } #if 0 // Unimplemented? // Save resolution int unit_type; png_uint_32 x_resolution, y_resolution; #endif png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points // Save the information to the file png_write_info(png_ptr, info_ptr); png_write_flush(png_ptr); // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Write the PNG // png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0); // Fill the data structure png_byte** row_pointers = new png_byte*[imageRect.height()]; int row = 0; for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) { KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width()); row_pointers[row] = new png_byte[imageRect.width() * device->pixelSize()]; switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(row_pointers[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } else { quint8 *dst = row_pointers[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(row_pointers[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } else { quint8 *dst = row_pointers[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { quint8 *dst = row_pointers[row]; KisPNGWriteStream writestream(dst, color_nb_bits); do { const quint8 *d = it->oldRawData(); int i; for (i = 0; i < num_palette; i++) { if (palette[i].red == d[2] && palette[i].green == d[1] && palette[i].blue == d[0]) { break; } } writestream.setNextValue(i); } while (it->nextPixel()); } break; default: delete[] row_pointers; return KisImageBuilder_RESULT_UNSUPPORTED; } } png_write_image(png_ptr, row_pointers); // Writing is over png_write_end(png_ptr, info_ptr); // Free memory png_destroy_write_struct(&png_ptr, &info_ptr); for (int y = 0; y < imageRect.height(); y++) { delete[] row_pointers[y]; } delete[] row_pointers; if (color_type == PNG_COLOR_TYPE_PALETTE) { delete [] palette; } return KisImageBuilder_RESULT_OK; } void KisPNGConverter::cancel() { m_stop = true; } void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass) { if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return; // setProgress(row_number); } bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs) { return colorSpaceIdSupported(cs->id()); } diff --git a/libs/ui/kis_splash_screen.cpp b/libs/ui/kis_splash_screen.cpp index dc083f6d7c..fbdc19283f 100644 --- a/libs/ui/kis_splash_screen.cpp +++ b/libs/ui/kis_splash_screen.cpp @@ -1,200 +1,204 @@ /* * Copyright (c) 2014 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_splash_screen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisSplashScreen::KisSplashScreen(const QString &version, const QPixmap &pixmap, bool themed, QWidget *parent, Qt::WindowFlags f) - : QWidget(parent, Qt::SplashScreen | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | f), + : QWidget(parent, Qt::SplashScreen | Qt::FramelessWindowHint +#ifdef Q_OS_LINUX + | Qt::WindowStaysOnTopHint +#endif + | f), m_themed(themed) { setupUi(this); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); // Maintain the aspect ratio on high DPI screens when scaling lblSplash->setPixmap(pixmap); setFixedWidth(pixmap.width()); QString color = colorString(); lblVersion->setText(i18n("Version: %1", version)); lblVersion->setStyleSheet("color:" + color); bnClose->hide(); connect(bnClose, SIGNAL(clicked()), this, SLOT(close())); chkShowAtStartup->hide(); connect(chkShowAtStartup, SIGNAL(toggled(bool)), this, SLOT(toggleShowAtStartup(bool))); KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false); chkShowAtStartup->setChecked(hideSplash); connect(lblRecent, SIGNAL(linkActivated(QString)), SLOT(linkClicked(QString))); connect(&m_timer, SIGNAL(timeout()), SLOT(raise())); // hide these labels by default lblLinks->setVisible(false); lblRecent->setVisible(false); line->setVisible(false); m_timer.setSingleShot(true); m_timer.start(10); } void KisSplashScreen::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); updateText(); } void KisSplashScreen::updateText() { QString color = colorString(); KConfigGroup cfg2( KSharedConfig::openConfig(), "RecentFiles"); int i = 1; QString recent = i18n("" "" "" "

Recent Files

", color); QString path; QStringList recentfiles; QFontMetrics metrics(lblRecent->font()); do { path = cfg2.readPathEntry(QString("File%1").arg(i), QString()); if (!path.isEmpty()) { QString name = cfg2.readPathEntry(QString("Name%1").arg(i), QString()); QUrl url(path); if (name.isEmpty()) { name = url.fileName(); } name = metrics.elidedText(name, Qt::ElideMiddle, lblRecent->width()); if (!url.isLocalFile() || QFile::exists(url.toLocalFile())) { recentfiles.insert(0, QString("

%2

").arg(path).arg(name).arg(color)); } } i++; } while (!path.isEmpty() || i <= 8); recent += recentfiles.join("\n"); recent += "" ""; lblRecent->setText(recent); } void KisSplashScreen::displayLinks() { QString color = colorString(); lblLinks->setTextFormat(Qt::RichText); lblLinks->setText(i18n("" "" "" "

Links

" "

Support Krita

" "

Getting Started

" "

Manual

" "

Krita Website

" "

User Community

" "

Source Code

" "

Krita on Steam

" "" "", color)); lblLinks->setVisible(true); updateText(); } void KisSplashScreen::displayRecentFiles() { lblRecent->setVisible(true); line->setVisible(true); } QString KisSplashScreen::colorString() const { QString color = "#FFFFFF"; if (m_themed && qApp->palette().background().color().value() > 100) { color = "#000000"; } return color; } void KisSplashScreen::repaint() { QWidget::repaint(); QApplication::flush(); } void KisSplashScreen::show() { QRect r(QPoint(), sizeHint()); resize(r.size()); move(QApplication::desktop()->availableGeometry().center() - r.center()); if (isVisible()) { repaint(); } m_timer.setSingleShot(true); m_timer.start(1); QWidget::show(); } void KisSplashScreen::toggleShowAtStartup(bool toggle) { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); cfg.writeEntry("HideSplashAfterStartup", toggle); } void KisSplashScreen::linkClicked(const QString &link) { KisPart::instance()->openExistingFile(QUrl::fromLocalFile(link)); if (isTopLevel()) { close(); } } diff --git a/libs/ui/kis_stopgradient_editor.cpp b/libs/ui/kis_stopgradient_editor.cpp index b185f040be..4e485ea63e 100644 --- a/libs/ui/kis_stopgradient_editor.cpp +++ b/libs/ui/kis_stopgradient_editor.cpp @@ -1,187 +1,189 @@ /* * Copyright (c) 2004 Cyrille Berger * 2016 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_stopgradient_editor.h" #include #include #include #include #include #include "kis_debug.h" #include /****************************** KisStopGradientEditor ******************************/ KisStopGradientEditor::KisStopGradientEditor(QWidget *parent) : QWidget(parent), m_gradient(0) { setupUi(this); connect(gradientSlider, SIGNAL(sigSelectedStop(int)), this, SLOT(stopChanged(int))); connect(nameedit, SIGNAL(editingFinished()), this, SLOT(nameChanged())); connect(colorButton, SIGNAL(changed(const KoColor&)), SLOT(colorChanged(const KoColor&))); opacitySlider->setPrefix(i18n("Opacity: ")); opacitySlider->setRange(0.0, 1.0, 2); connect(opacitySlider, SIGNAL(valueChanged(qreal)), this, SLOT(opacityChanged(qreal))); - buttonReverse->setIcon(KisIconUtils::loadIcon("mirrorAxis-HorizontalMove")); + buttonReverse->setIcon(KisIconUtils::loadIcon("view-refresh")); + buttonReverse->setToolTip(i18n("Flip Gradient")); KisIconUtils::updateIcon(buttonReverse); connect(buttonReverse, SIGNAL(pressed()), SLOT(reverse())); - buttonReverseSecond->setIcon(KisIconUtils::loadIcon("mirrorAxis-HorizontalMove")); + buttonReverseSecond->setIcon(KisIconUtils::loadIcon("view-refresh")); + buttonReverseSecond->setToolTip(i18n("Flip Gradient")); KisIconUtils::updateIcon(buttonReverseSecond); connect(buttonReverseSecond, SIGNAL(clicked()), SLOT(reverse())); setCompactMode(false); setGradient(0); stopChanged(-1); } KisStopGradientEditor::KisStopGradientEditor(KoStopGradient* gradient, QWidget *parent, const char* name, const QString& caption) : KisStopGradientEditor(parent) { setObjectName(name); setWindowTitle(caption); setGradient(gradient); } void KisStopGradientEditor::setCompactMode(bool value) { lblName->setVisible(!value); buttonReverse->setVisible(!value); nameedit->setVisible(!value); buttonReverseSecond->setVisible(value); } void KisStopGradientEditor::setGradient(KoStopGradient *gradient) { m_gradient = gradient; setEnabled(m_gradient); if (m_gradient) { gradientSlider->setGradientResource(m_gradient); nameedit->setText(gradient->name()); stopChanged(gradientSlider->selectedStop()); } emit sigGradientChanged(); } void KisStopGradientEditor::notifyGlobalColorChanged(const KoColor &color) { if (colorButton->isEnabled() && color != colorButton->color()) { colorButton->setColor(color); } } boost::optional KisStopGradientEditor::currentActiveStopColor() const { if (!colorButton->isEnabled()) return boost::none; return colorButton->color(); } void KisStopGradientEditor::stopChanged(int stop) { const bool hasStopSelected = stop >= 0; opacitySlider->setEnabled(hasStopSelected); colorButton->setEnabled(hasStopSelected); stopLabel->setEnabled(hasStopSelected); if (hasStopSelected) { KoColor color = m_gradient->stops()[stop].second; opacitySlider->setValue(color.opacityF()); color.setOpacity(1.0); colorButton->setColor(color); } emit sigGradientChanged(); } void KisStopGradientEditor::colorChanged(const KoColor& color) { QList stops = m_gradient->stops(); int currentStop = gradientSlider->selectedStop(); double t = stops[currentStop].first; KoColor c(color, stops[currentStop].second.colorSpace()); c.setOpacity(stops[currentStop].second.opacityU8()); stops.removeAt(currentStop); stops.insert(currentStop, KoGradientStop(t, c)); m_gradient->setStops(stops); gradientSlider->update(); emit sigGradientChanged(); } void KisStopGradientEditor::opacityChanged(qreal value) { QList stops = m_gradient->stops(); int currentStop = gradientSlider->selectedStop(); double t = stops[currentStop].first; KoColor c = stops[currentStop].second; c.setOpacity(value); stops.removeAt(currentStop); stops.insert(currentStop, KoGradientStop(t, c)); m_gradient->setStops(stops); gradientSlider->update(); emit sigGradientChanged(); } void KisStopGradientEditor::nameChanged() { m_gradient->setName(nameedit->text()); emit sigGradientChanged(); } void KisStopGradientEditor::reverse() { QList stops = m_gradient->stops(); QList reversedStops; for(const KoGradientStop& stop : stops) { reversedStops.push_front(KoGradientStop(1 - stop.first, stop.second)); } m_gradient->setStops(reversedStops); gradientSlider->setSelectedStop(stops.size() - 1 - gradientSlider->selectedStop()); emit sigGradientChanged(); } diff --git a/libs/ui/tool/kis_tool_freehand.cc b/libs/ui/tool/kis_tool_freehand.cc index d843c15b94..47b93df966 100644 --- a/libs/ui/tool/kis_tool_freehand.cc +++ b/libs/ui/tool/kis_tool_freehand.cc @@ -1,457 +1,458 @@ /* * kis_tool_freehand.cc - part of Krita * * Copyright (c) 2003-2007 Boudewijn Rempt * Copyright (c) 2004 Bart Coppens * Copyright (c) 2007,2008,2010 Cyrille Berger * Copyright (c) 2009 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_freehand.h" #include #include #include #include #include #include #include #include #include #include //pop up palette #include // Krita/image #include #include #include #include #include #include // Krita/ui #include "kis_abstract_perspective_grid.h" #include "kis_config.h" #include "canvas/kis_canvas2.h" #include "kis_cursor.h" #include #include #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "kis_recording_adapter.h" #include "strokes/freehand_stroke.h" using namespace std::placeholders; // For _1 placeholder KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText) : KisToolPaint(canvas, cursor), m_paintopBasedPickingInAction(false), m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1)) { m_assistant = false; m_magnetism = 1.0; m_only_one_assistant = true; setSupportOutline(true); setMaskSyntheticEvents(true); // Disallow mouse events from finger presses. m_infoBuilder = new KisToolFreehandPaintingInformationBuilder(this); m_recordingAdapter = new KisRecordingAdapter(); m_helper = new KisToolFreehandHelper(m_infoBuilder, transactionText, m_recordingAdapter); connect(m_helper, SIGNAL(requestExplicitUpdateOutline()), SLOT(explicitUpdateOutline())); } KisToolFreehand::~KisToolFreehand() { delete m_helper; delete m_recordingAdapter; delete m_infoBuilder; } KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const { return m_helper->smoothingOptions(); } void KisToolFreehand::resetCursorStyle() { KisConfig cfg; switch (cfg.newCursorStyle()) { case CURSOR_STYLE_NO_CURSOR: useCursor(KisCursor::blankCursor()); break; case CURSOR_STYLE_POINTER: useCursor(KisCursor::arrowCursor()); break; case CURSOR_STYLE_SMALL_ROUND: useCursor(KisCursor::roundCursor()); break; case CURSOR_STYLE_CROSSHAIR: useCursor(KisCursor::crossCursor()); break; case CURSOR_STYLE_TRIANGLE_RIGHTHANDED: useCursor(KisCursor::triangleRightHandedCursor()); break; case CURSOR_STYLE_TRIANGLE_LEFTHANDED: useCursor(KisCursor::triangleLeftHandedCursor()); break; case CURSOR_STYLE_BLACK_PIXEL: useCursor(KisCursor::pixelBlackCursor()); break; case CURSOR_STYLE_WHITE_PIXEL: useCursor(KisCursor::pixelWhiteCursor()); break; case CURSOR_STYLE_TOOLICON: default: KisToolPaint::resetCursorStyle(); break; } } KisPaintingInformationBuilder* KisToolFreehand::paintingInformationBuilder() const { return m_infoBuilder; } KisRecordingAdapter* KisToolFreehand::recordingAdapter() const { return m_recordingAdapter; } void KisToolFreehand::resetHelper(KisToolFreehandHelper *helper) { delete m_helper; m_helper = helper; } int KisToolFreehand::flags() const { - return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET; + return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET + |KisTool::FLAG_USES_CUSTOM_SIZE; } void KisToolFreehand::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); } void KisToolFreehand::deactivate() { if (mode() == PAINT_MODE) { endStroke(); setMode(KisTool::HOVER_MODE); } KisToolPaint::deactivate(); } void KisToolFreehand::initStroke(KoPointerEvent *event) { m_helper->initPaint(event, canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolFreehand::doStroke(KoPointerEvent *event) { //set canvas information here?// KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { m_helper->setCanvasHorizontalMirrorState(canvas2->xAxisMirrored()); m_helper->setCanvasRotation(canvas2->rotationAngle()); } m_helper->paint(event); } void KisToolFreehand::endStroke() { m_helper->endPaint(); } bool KisToolFreehand::primaryActionSupportsHiResEvents() const { return true; } void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event) { // FIXME: workaround for the Duplicate Op tryPickByPaintOp(event, PickFgImage); requestUpdateOutline(event->point, event); NodePaintAbility paintability = nodePaintAbility(); if (!nodeEditable() || paintability != PAINT) { if(paintability == KisToolPaint::VECTOR){ KisCanvas2 * kiscanvas = static_cast(canvas()); QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask."); kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); } event->ignore(); return; } setMode(KisTool::PAINT_MODE); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->disableControls(); } initStroke(event); } void KisToolFreehand::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); requestUpdateOutline(event->point, event); /** * Actual painting */ doStroke(event); } void KisToolFreehand::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); endStroke(); if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->endStroke(); } notifyModified(); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->enableControls(); } setMode(KisTool::HOVER_MODE); } bool KisToolFreehand::tryPickByPaintOp(KoPointerEvent *event, AlternateAction action) { if (action != PickFgNode && action != PickFgImage) return false; /** * FIXME: we need some better way to implement modifiers * for a paintop level. This method is used in DuplicateOp only! */ QPointF pos = adjustPosition(event->point, event->point); qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->resourceProvider()->perspectiveGrids()) { if (grid && grid->contains(pos)) { perspective = grid->distance(pos); break; } } if (!currentPaintOpPreset()) { return false; } bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()-> mousePressEvent(KisPaintInformation(convertToPixelCoord(event->point), pressureToCurve(event->pressure()), event->xTilt(), event->yTilt(), event->rotation(), event->tangentialPressure(), perspective, 0, 0), event->modifiers(), currentNode()); return !paintOpIgnoredEvent; } void KisToolFreehand::activateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::activateAlternateAction(action); return; } useCursor(KisCursor::blankCursor()); setOutlineEnabled(true); } void KisToolFreehand::deactivateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::deactivateAlternateAction(action); return; } resetCursorStyle(); setOutlineEnabled(false); } void KisToolFreehand::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action)) { m_paintopBasedPickingInAction = true; return; } if (action != ChangeSize) { KisToolPaint::beginAlternateAction(event, action); return; } setMode(GESTURE_MODE); m_initialGestureDocPoint = event->point; m_initialGestureGlobalPoint = QCursor::pos(); m_lastDocumentPoint = event->point; m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize(); } void KisToolFreehand::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) return; if (action != ChangeSize) { KisToolPaint::continueAlternateAction(event, action); return; } QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint); QPointF actualWidgetPosition = convertDocumentToWidget(event->point); QPointF offset = actualWidgetPosition - lastWidgetPosition; KisCanvas2 *canvas2 = dynamic_cast(canvas()); QRect screenRect = QApplication::desktop()->screenGeometry(); qreal scaleX = 0; qreal scaleY = 0; canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY); const qreal maxBrushSize = KisConfig().readEntry("maximumBrushSize", 1000); const qreal effectiveMaxDragSize = 0.5 * screenRect.width(); const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX); const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize; const qreal sizeDiff = scaleCoeff * offset.x() ; if (qAbs(sizeDiff) > 0.01) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); const qreal newSize = qBound(0.01, m_lastPaintOpSize + sizeDiff, maxBrushSize); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); //m_brushResizeCompressor.start(newSize); m_lastDocumentPoint = event->point; m_lastPaintOpSize = newSize; } } void KisToolFreehand::endAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) { m_paintopBasedPickingInAction = false; return; } if (action != ChangeSize) { KisToolPaint::endAlternateAction(event, action); return; } QCursor::setPos(m_initialGestureGlobalPoint); requestUpdateOutline(m_initialGestureDocPoint, 0); setMode(HOVER_MODE); } bool KisToolFreehand::wantsAutoScroll() const { return false; } void KisToolFreehand::setAssistant(bool assistant) { m_assistant = assistant; } void KisToolFreehand::setOnlyOneAssistantSnap(bool assistant) { m_only_one_assistant = assistant; } void KisToolFreehand::slotDoResizeBrush(qreal newSize) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); } QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin) { if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(m_only_one_assistant); QPointF ap = static_cast(canvas())->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin); return (1.0 - m_magnetism) * point + m_magnetism * ap; } return point; } qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint) { qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->resourceProvider()->perspectiveGrids()) { if (grid && grid->contains(documentPoint)) { perspective = grid->distance(documentPoint); break; } } return perspective; } void KisToolFreehand::explicitUpdateOutline() { requestUpdateOutline(m_outlineDocPoint, 0); } QPainterPath KisToolFreehand::getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) { QPointF imagePos = currentImage()->documentToPixel(documentPos); if (currentPaintOpPreset()) return m_helper->paintOpOutline(imagePos, event, currentPaintOpPreset()->settings(), outlineMode); else return QPainterPath(); } diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc index 97e6ef1c2a..306a84a914 100644 --- a/libs/ui/tool/kis_tool_shape.cc +++ b/libs/ui/tool/kis_tool_shape.cc @@ -1,254 +1,255 @@ /* * Copyright (c) 2005 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_shape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include #include KisToolShape::KisToolShape(KoCanvasBase * canvas, const QCursor & cursor) : KisToolPaint(canvas, cursor) { m_shapeOptionsWidget = 0; } KisToolShape::~KisToolShape() { // in case the widget hasn't been shown if (m_shapeOptionsWidget && !m_shapeOptionsWidget->parent()) { delete m_shapeOptionsWidget; } } void KisToolShape::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } int KisToolShape::flags() const { - return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET; + return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET + |KisTool::FLAG_USES_CUSTOM_SIZE; } QWidget * KisToolShape::createOptionWidget() { m_shapeOptionsWidget = new WdgGeometryOptions(0); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(KisPainter::StrokeStyleBrush); //connect two combo box event. Inherited classes can call the slots to make appropriate changes connect(m_shapeOptionsWidget->cmbOutline, SIGNAL(currentIndexChanged(int)), this, SLOT(outlineSettingChanged(int))); connect(m_shapeOptionsWidget->cmbFill, SIGNAL(currentIndexChanged(int)), this, SLOT(fillSettingChanged(int))); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(m_configGroup.readEntry("outlineType", 0)); m_shapeOptionsWidget->cmbFill->setCurrentIndex(m_configGroup.readEntry("fillType", 0)); //if both settings are empty, force the outline to brush so the tool will work when first activated if ( m_shapeOptionsWidget->cmbFill->currentIndex() == 0 && m_shapeOptionsWidget->cmbOutline->currentIndex() == 0) { m_shapeOptionsWidget->cmbOutline->setCurrentIndex(1); // brush } return m_shapeOptionsWidget; } void KisToolShape::outlineSettingChanged(int value) { m_configGroup.writeEntry("outlineType", value); } void KisToolShape::fillSettingChanged(int value) { m_configGroup.writeEntry("fillType", value); } KisPainter::FillStyle KisToolShape::fillStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbFill->currentIndex()); } else { return KisPainter::FillStyleNone; } } KisPainter::StrokeStyle KisToolShape::strokeStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbOutline->currentIndex()); } else { return KisPainter::StrokeStyleNone; } } qreal KisToolShape::currentStrokeWidth() const { const qreal sizeInPx = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal(); return canvas()->unit().fromUserValue(sizeInPx); } void KisToolShape::setupPaintAction(KisRecordedPaintAction* action) { KisToolPaint::setupPaintAction(action); action->setFillStyle(fillStyle()); action->setStrokeStyle(strokeStyle()); action->setGenerator(currentGenerator()); action->setPattern(currentPattern()); action->setGradient(currentGradient()); } void KisToolShape::addShape(KoShape* shape) { KoImageCollection* imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); switch(fillStyle()) { case KisPainter::FillStyleForegroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentFgColor().toQColor()))); break; case KisPainter::FillStyleBackgroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentBgColor().toQColor()))); break; case KisPainter::FillStylePattern: if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); if (currentPattern()) { fill->setPattern(currentPattern()->pattern()); shape->setBackground(fill); } } else { shape->setBackground(QSharedPointer(0)); } break; case KisPainter::FillStyleGradient: { QLinearGradient *gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1)); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); gradient->setStops(currentGradient()->toQGradient()->stops()); QSharedPointer gradientFill(new KoGradientBackground(gradient)); shape->setBackground(gradientFill); } break; case KisPainter::FillStyleNone: default: shape->setBackground(QSharedPointer(0)); break; } KUndo2Command * cmd = canvas()->shapeController()->addShape(shape); canvas()->addCommand(cmd); } void KisToolShape::addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name) { KisNodeSP node = currentNode(); if (!node || !blockUntilOperationsFinished()) { return; } // Get painting options KisPaintOpPresetSP preset = currentPaintOpPreset(); // Compute the outline KisImageSP image = this->image(); QTransform matrix; matrix.scale(image->xRes(), image->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); QPainterPath mapedOutline = matrix.map(pathShape->outline()); // Recorde the paint action KisRecordedPathPaintAction bezierCurvePaintAction( KisNodeQueryPath::absolutePath(node), preset ); bezierCurvePaintAction.setPaintColor(currentFgColor()); QPointF lastPoint, nextPoint; int elementCount = mapedOutline.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = mapedOutline.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: if (i != 0) { qFatal("Unhandled"); // XXX: I am not sure the tool can produce such element, deal with it when it can } lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); bezierCurvePaintAction.addLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint)); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(mapedOutline.elementAt(i + 2).x, mapedOutline.elementAt(i + 2).y); bezierCurvePaintAction.addCurve(KisPaintInformation(lastPoint), QPointF(mapedOutline.elementAt(i).x, mapedOutline.elementAt(i).y), QPointF(mapedOutline.elementAt(i + 1).x, mapedOutline.elementAt(i + 1).y), KisPaintInformation(nextPoint)); lastPoint = nextPoint; break; default: continue; } } image->actionRecorder()->addAction(bezierCurvePaintAction); if (node->hasEditablePaintDevice()) { KisFigurePaintingToolHelper helper(name, image, node, canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPainterPath(mapedOutline); } else if (node->inherits("KisShapeLayer")) { pathShape->normalize(); addShape(pathShape); } notifyModified(); } diff --git a/libs/ui/widgets/KoFillConfigWidget.cpp b/libs/ui/widgets/KoFillConfigWidget.cpp index 358dff5efc..ce58458922 100644 --- a/libs/ui/widgets/KoFillConfigWidget.cpp +++ b/libs/ui/widgets/KoFillConfigWidget.cpp @@ -1,749 +1,804 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2012 Jean-Nicolas Artaud * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoFillConfigWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceServerProvider.h" #include "KoResourceServerAdapter.h" #include "KoResourceSelector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoZoomHandler.h" #include "KoColorPopupButton.h" #include "ui_KoFillConfigWidget.h" #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include #include #include #include "kis_global.h" #include "kis_debug.h" static const char* const buttonnone[]={ "16 16 3 1", "# c #000000", "e c #ff0000", "- c #ffffff", "################", "#--------------#", "#-e----------e-#", "#--e--------e--#", "#---e------e---#", "#----e----e----#", "#-----e--e-----#", "#------ee------#", "#------ee------#", "#-----e--e-----#", "#----e----e----#", "#---e------e---#", "#--e--------e--#", "#-e----------e-#", "#--------------#", "################"}; static const char* const buttonsolid[]={ "16 16 2 1", "# c #000000", ". c #969696", "################", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "################"}; // FIXME: Smoother gradient button. static const char* const buttongradient[]={ "16 16 15 1", "# c #000000", "n c #101010", "m c #202020", "l c #303030", "k c #404040", "j c #505050", "i c #606060", "h c #707070", "g c #808080", "f c #909090", "e c #a0a0a0", "d c #b0b0b0", "c c #c0c0c0", "b c #d0d0d0", "a c #e0e0e0", "################", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "################"}; static const char* const buttonpattern[]={ "16 16 4 1", ". c #0a0a0a", "# c #333333", "a c #a0a0a0", "b c #ffffffff", "################", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "################"}; class Q_DECL_HIDDEN KoFillConfigWidget::Private { public: Private(KoFlake::FillVariant _fillVariant) : canvas(0), colorChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE), gradientChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE), fillVariant(_fillVariant), noSelectionTrackingMode(false) { } KoColorPopupAction *colorAction; KoResourcePopupAction *gradientAction; KoResourcePopupAction *patternAction; QButtonGroup *group; KoCanvasBase *canvas; KisSignalCompressor colorChangedCompressor; KisAcyclicSignalConnector shapeChangedAcyclicConnector; KisAcyclicSignalConnector resourceManagerAcyclicConnector; + KoFillConfigWidget::StyleButton selectedFillIndex; QSharedPointer activeGradient; KisSignalCompressor gradientChangedCompressor; KoFlake::FillVariant fillVariant; bool noSelectionTrackingMode; Ui_KoFillConfigWidget *ui; std::vector deactivationLocks; }; KoFillConfigWidget::KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, QWidget *parent) : QWidget(parent) , d(new Private(fillVariant)) { d->canvas = canvas; d->shapeChangedAcyclicConnector.connectBackwardVoid( d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeChanged())); d->shapeChangedAcyclicConnector.connectBackwardVoid( d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(shapeChanged())); d->resourceManagerAcyclicConnector.connectBackwardResourcePair( d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); d->resourceManagerAcyclicConnector.connectForwardVoid( this, SIGNAL(sigInternalRequestColorToResourceManager()), this, SLOT(slotProposeCurrentColorToResourceManager())); // confure GUI d->ui = new Ui_KoFillConfigWidget(); d->ui->setupUi(this); d->group = new QButtonGroup(this); d->group->setExclusive(true); d->ui->btnNoFill->setIcon(QPixmap((const char **) buttonnone)); d->group->addButton(d->ui->btnNoFill, None); d->ui->btnSolidFill->setIcon(QPixmap((const char **) buttonsolid)); d->group->addButton(d->ui->btnSolidFill, Solid); d->ui->btnGradientFill->setIcon(QPixmap((const char **) buttongradient)); d->group->addButton(d->ui->btnGradientFill, Gradient); d->ui->btnPatternFill->setIcon(QPixmap((const char **) buttonpattern)); d->group->addButton(d->ui->btnPatternFill, Pattern); d->colorAction = new KoColorPopupAction(d->ui->btnChooseSolidColor); d->colorAction->setToolTip(i18n("Change the filling color")); d->colorAction->setCurrentColor(Qt::white); d->ui->btnChooseSolidColor->setDefaultAction(d->colorAction); d->ui->btnChooseSolidColor->setPopupMode(QToolButton::InstantPopup); d->ui->btnSolidColorPick->setIcon(KisIconUtils::loadIcon("krita_tool_color_picker")); // TODO: for now the color picking button is disabled! d->ui->btnSolidColorPick->setEnabled(false); connect(d->colorAction, SIGNAL(colorChanged(const KoColor &)), &d->colorChangedCompressor, SLOT(start())); connect(&d->colorChangedCompressor, SIGNAL(timeout()), SLOT(colorChanged())); connect(d->ui->btnChooseSolidColor, SIGNAL(iconSizeChanged()), d->colorAction, SLOT(updateIcon())); connect(d->group, SIGNAL(buttonClicked(int)), SLOT(styleButtonPressed(int))); - connect(d->ui->stackWidget, SIGNAL(currentChanged(int)), SLOT(slotUpdateFillTitle())); + + connect(d->group, SIGNAL(buttonClicked(int)), SLOT(slotUpdateFillTitle())); + slotUpdateFillTitle(); styleButtonPressed(d->group->checkedId()); + // Gradient selector d->ui->wdgGradientEditor->setCompactMode(true); connect(d->ui->wdgGradientEditor, SIGNAL(sigGradientChanged()), &d->gradientChangedCompressor, SLOT(start())); connect(&d->gradientChangedCompressor, SIGNAL(timeout()), SLOT(activeGradientChanged())); KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance(); QSharedPointer gradientResourceAdapter( new KoResourceServerAdapter(serverProvider->gradientServer())); d->gradientAction = new KoResourcePopupAction(gradientResourceAdapter, d->ui->btnChoosePredefinedGradient); d->gradientAction->setToolTip(i18n("Change filling gradient")); d->ui->btnChoosePredefinedGradient->setDefaultAction(d->gradientAction); d->ui->btnChoosePredefinedGradient->setPopupMode(QToolButton::InstantPopup); connect(d->gradientAction, SIGNAL(resourceSelected(QSharedPointer )), SLOT(gradientResourceChanged())); connect(d->ui->btnChoosePredefinedGradient, SIGNAL(iconSizeChanged()), d->gradientAction, SLOT(updateIcon())); d->ui->btnSaveGradient->setIcon(KisIconUtils::loadIcon("document-save")); connect(d->ui->btnSaveGradient, SIGNAL(clicked()), SLOT(slotSavePredefinedGradientClicked())); connect(d->ui->cmbGradientRepeat, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientRepeatChanged())); connect(d->ui->cmbGradientType, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientTypeChanged())); deactivate(); #if 0 // Pattern selector QSharedPointerpatternResourceAdapter(new KoResourceServerAdapter(serverProvider->patternServer())); d->patternAction = new KoResourcePopupAction(patternResourceAdapter, d->colorButton); d->patternAction->setToolTip(i18n("Change the filling pattern")); connect(d->patternAction, SIGNAL(resourceSelected(QSharedPointer )), this, SLOT(patternChanged(QSharedPointer ))); connect(d->colorButton, SIGNAL(iconSizeChanged()), d->patternAction, SLOT(updateIcon())); #endif } KoFillConfigWidget::~KoFillConfigWidget() { delete d; } void KoFillConfigWidget::activate() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty()); d->deactivationLocks.clear(); if (!d->noSelectionTrackingMode) { shapeChanged(); } else { loadCurrentFillFromResourceServer(); } } void KoFillConfigWidget::deactivate() { KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty()); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector)); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector)); } - void KoFillConfigWidget::setNoSelectionTrackingMode(bool value) { d->noSelectionTrackingMode = value; if (!d->noSelectionTrackingMode) { shapeChanged(); } } void KoFillConfigWidget::slotUpdateFillTitle() { QString text = d->group->checkedButton() ? d->group->checkedButton()->text() : QString(); text.replace('&', QString()); d->ui->lblFillTitle->setText(text); } void KoFillConfigWidget::slotCanvasResourceChanged(int key, const QVariant &value) { if ((key == KoCanvasResourceManager::ForegroundColor && d->fillVariant == KoFlake::Fill) || (key == KoCanvasResourceManager::BackgroundColor && d->fillVariant == KoFlake::StrokeFill && !d->noSelectionTrackingMode) || (key == KoCanvasResourceManager::ForegroundColor && d->noSelectionTrackingMode)) { KoColor color = value.value(); const int checkedId = d->group->checkedId(); if ((checkedId < 0 || checkedId == None || checkedId == Solid) && !(checkedId == Solid && d->colorAction->currentKoColor() == color)) { d->group->button(Solid)->setChecked(true); - d->ui->stackWidget->setCurrentIndex(Solid); + d->selectedFillIndex = Solid; + d->colorAction->setCurrentColor(color); d->colorChangedCompressor.start(); } else if (checkedId == Gradient && key == KoCanvasResourceManager::ForegroundColor) { d->ui->wdgGradientEditor->notifyGlobalColorChanged(color); } } else if (key == KisCanvasResourceProvider::CurrentGradient) { KoResource *gradient = value.value(); const int checkedId = d->group->checkedId(); if (gradient && (checkedId < 0 || checkedId == None || checkedId == Gradient)) { d->group->button(Gradient)->setChecked(true); d->gradientAction->setCurrentResource(gradient); } } } QList KoFillConfigWidget::currentShapes() { return d->canvas->selectedShapesProxy()->selection()->selectedEditableShapes(); } +int KoFillConfigWidget::selectedFillIndex() { + return d->selectedFillIndex; +} + void KoFillConfigWidget::styleButtonPressed(int buttonId) { switch (buttonId) { case KoFillConfigWidget::None: noColorSelected(); break; case KoFillConfigWidget::Solid: colorChanged(); break; case KoFillConfigWidget::Gradient: if (d->activeGradient) { activeGradientChanged(); } else { gradientResourceChanged(); } break; case KoFillConfigWidget::Pattern: // Only select mode in the widget, don't set actual pattern :/ //d->colorButton->setDefaultAction(d->patternAction); //patternChanged(d->patternAction->currentBackground()); break; } if (buttonId >= None && buttonId <= Pattern) { - d->ui->stackWidget->setCurrentIndex(buttonId); + d->selectedFillIndex = static_cast(buttonId); } } KoShapeStrokeSP KoFillConfigWidget::createShapeStroke() { KoShapeStrokeSP stroke(new KoShapeStroke()); KIS_ASSERT_RECOVER_RETURN_VALUE(d->fillVariant == KoFlake::StrokeFill, stroke); switch (d->group->checkedId()) { case KoFillConfigWidget::None: stroke->setColor(Qt::transparent); break; case KoFillConfigWidget::Solid: stroke->setColor(d->colorAction->currentColor()); break; case KoFillConfigWidget::Gradient: { QScopedPointer g(d->activeGradient->toQGradient()); QBrush newBrush = *g; stroke->setLineBrush(newBrush); stroke->setColor(Qt::transparent); break; } case KoFillConfigWidget::Pattern: break; } return stroke; } void KoFillConfigWidget::noColorSelected() { KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigFillChanged(); return; } KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); KUndo2Command *command = wrapper.setColor(QColor()); if (command) { d->canvas->addCommand(command); } emit sigFillChanged(); } void KoFillConfigWidget::colorChanged() { KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigInternalRequestColorToResourceManager(); emit sigFillChanged(); return; } KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); KUndo2Command *command = wrapper.setColor(d->colorAction->currentColor()); if (command) { d->canvas->addCommand(command); } emit sigInternalRequestColorToResourceManager(); emit sigFillChanged(); } void KoFillConfigWidget::slotProposeCurrentColorToResourceManager() { const int checkedId = d->group->checkedId(); bool hasColor = false; KoColor color; KoCanvasResourceManager::CanvasResource colorSlot = KoCanvasResourceManager::ForegroundColor; if (checkedId == Solid) { if (d->fillVariant == KoFlake::StrokeFill) { colorSlot = KoCanvasResourceManager::BackgroundColor; } color = d->colorAction->currentKoColor(); hasColor = true; } else if (checkedId == Gradient) { if (boost::optional gradientColor = d->ui->wdgGradientEditor->currentActiveStopColor()) { color = *gradientColor; hasColor = true; } } if (hasColor) { d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(color)); } } template QString findFirstAvailableResourceName(const QString &baseName, ResourceServer *server) { if (!server->resourceByName(baseName)) return baseName; int counter = 1; QString result; while ((result = QString("%1%2").arg(baseName).arg(counter)), server->resourceByName(result)) { counter++; } return result; } void KoFillConfigWidget::slotSavePredefinedGradientClicked() { KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance(); auto server = serverProvider->gradientServer(); const QString defaultGradientNamePrefix = i18nc("default prefix for the saved gradient", "gradient"); QString name = d->activeGradient->name().isEmpty() ? defaultGradientNamePrefix : d->activeGradient->name(); name = findFirstAvailableResourceName(name, server); name = QInputDialog::getText(this, i18nc("@title:window", "Save Gradient"), i18n("Enter gradient name:"), QLineEdit::Normal, name); // TODO: currently we do not allow the user to // create two resources with the same name! // Please add some feedback for it! name = findFirstAvailableResourceName(name, server); d->activeGradient->setName(name); const QString saveLocation = server->saveLocation(); d->activeGradient->setFilename(saveLocation + d->activeGradient->name() + d->activeGradient->defaultFileExtension()); KoAbstractGradient *newGradient = d->activeGradient->clone(); server->addResource(newGradient); d->gradientAction->setCurrentResource(newGradient); } void KoFillConfigWidget::activeGradientChanged() { setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); emit sigInternalRequestColorToResourceManager(); } void KoFillConfigWidget::gradientResourceChanged() { QSharedPointer bg = qSharedPointerDynamicCast( d->gradientAction->currentBackground()); uploadNewGradientBackground(bg->gradient()); setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); } void KoFillConfigWidget::slotGradientTypeChanged() { QGradient::Type type = d->ui->cmbGradientType->currentIndex() == 0 ? QGradient::LinearGradient : QGradient::RadialGradient; d->activeGradient->setType(type); activeGradientChanged(); } void KoFillConfigWidget::slotGradientRepeatChanged() { QGradient::Spread spread = QGradient::Spread(d->ui->cmbGradientRepeat->currentIndex()); d->activeGradient->setSpread(spread); activeGradientChanged(); } void KoFillConfigWidget::uploadNewGradientBackground(const QGradient *gradient) { KisSignalsBlocker b1(d->ui->wdgGradientEditor, d->ui->cmbGradientType, d->ui->cmbGradientRepeat); d->ui->wdgGradientEditor->setGradient(0); d->activeGradient.reset(KoStopGradient::fromQGradient(gradient)); d->ui->wdgGradientEditor->setGradient(d->activeGradient.data()); d->ui->cmbGradientType->setCurrentIndex(d->activeGradient->type() != QGradient::LinearGradient); d->ui->cmbGradientRepeat->setCurrentIndex(int(d->activeGradient->spread())); } void KoFillConfigWidget::setNewGradientBackgroundToShape() { QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigFillChanged(); return; } KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); QScopedPointer srcQGradient(d->activeGradient->toQGradient()); KUndo2Command *command = wrapper.applyGradientStopsOnly(srcQGradient.data()); if (command) { d->canvas->addCommand(command); } emit sigFillChanged(); } void KoFillConfigWidget::updateGradientSaveButtonAvailability() { bool savingEnabled = false; QScopedPointer currentGradient(d->activeGradient->toQGradient()); QSharedPointer bg = d->gradientAction->currentBackground(); if (bg) { QSharedPointer resourceBackground = qSharedPointerDynamicCast(bg); savingEnabled = resourceBackground->gradient()->stops() != currentGradient->stops(); savingEnabled |= resourceBackground->gradient()->type() != currentGradient->type(); savingEnabled |= resourceBackground->gradient()->spread() != currentGradient->spread(); } d->ui->btnSaveGradient->setEnabled(savingEnabled); } void KoFillConfigWidget::patternChanged(QSharedPointer background) { Q_UNUSED(background); #if 0 QSharedPointer patternBackground = qSharedPointerDynamicCast(background); if (! patternBackground) { return; } QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { return; } KoImageCollection *imageCollection = d->canvas->shapeController()->resourceManager()->imageCollection(); if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); fill->setPattern(patternBackground->pattern()); d->canvas->addCommand(new KoShapeBackgroundCommand(selectedShapes, fill)); } #endif } void KoFillConfigWidget::loadCurrentFillFromResourceServer() { { KoColor color = d->canvas->resourceManager()->backgroundColor(); slotCanvasResourceChanged(KoCanvasResourceManager::BackgroundColor, QVariant::fromValue(color)); } { KoColor color = d->canvas->resourceManager()->foregroundColor(); slotCanvasResourceChanged(KoCanvasResourceManager::ForegroundColor, QVariant::fromValue(color)); } Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(true); } emit sigFillChanged(); } void KoFillConfigWidget::shapeChanged() { QList shapes = currentShapes(); if (shapes.isEmpty() || (shapes.size() > 1 && KoShapeFillWrapper(shapes, d->fillVariant).isMixedFill())) { Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(!shapes.isEmpty()); } d->group->button(None)->setChecked(true); - d->ui->stackWidget->setCurrentIndex(None); + d->selectedFillIndex = None; } else { Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(true); } KoShape *shape = shapes.first(); updateWidget(shape); } } void KoFillConfigWidget::updateWidget(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); StyleButton newActiveButton = None; KoShapeFillWrapper wrapper(shape, d->fillVariant); switch (wrapper.type()) { case KoFlake::None: break; case KoFlake::Solid: { QColor color = wrapper.color(); if (color.alpha() > 0) { newActiveButton = KoFillConfigWidget::Solid; d->colorAction->setCurrentColor(wrapper.color()); } break; } case KoFlake::Gradient: newActiveButton = KoFillConfigWidget::Gradient; uploadNewGradientBackground(wrapper.gradient()); updateGradientSaveButtonAvailability(); break; case KoFlake::Pattern: newActiveButton = KoFillConfigWidget::Pattern; break; } d->group->button(newActiveButton)->setChecked(true); - d->ui->stackWidget->setCurrentIndex(newActiveButton); + + d->selectedFillIndex = newActiveButton; + updateWidgetComponentVisbility(); +} + + +void KoFillConfigWidget::updateWidgetComponentVisbility() +{ + // The UI is showing/hiding things like this because the 'stacked widget' isn't very flexible + // and makes it difficult to put anything underneath it without a lot empty space + + // hide everything first + d->ui->wdgGradientEditor->setVisible(false); + d->ui->btnChoosePredefinedGradient->setVisible(false); + d->ui->btnChooseSolidColor->setVisible(false); + d->ui->typeLabel->setVisible(false); + d->ui->repeatLabel->setVisible(false); + d->ui->cmbGradientRepeat->setVisible(false); + d->ui->cmbGradientType->setVisible(false); + d->ui->btnSolidColorPick->setVisible(false); + d->ui->btnSaveGradient->setVisible(false); + d->ui->gradientTypeLine->setVisible(false); + d->ui->soldStrokeColorLabel->setVisible(false); + d->ui->presetLabel->setVisible(false); + + switch (d->selectedFillIndex) { + case KoFillConfigWidget::None: + break; + case KoFillConfigWidget::Solid: + d->ui->btnChooseSolidColor->setVisible(true); + d->ui->btnSolidColorPick->setVisible(true); + d->ui->soldStrokeColorLabel->setVisible(true); + break; + case KoFillConfigWidget::Gradient: + d->ui->wdgGradientEditor->setVisible(true); + d->ui->btnChoosePredefinedGradient->setVisible(true); + d->ui->typeLabel->setVisible(true); + d->ui->repeatLabel->setVisible(true); + d->ui->cmbGradientRepeat->setVisible(true); + d->ui->cmbGradientType->setVisible(true); + d->ui->btnSaveGradient->setVisible(true); + d->ui->gradientTypeLine->setVisible(true); + d->ui->presetLabel->setVisible(true); + break; + case KoFillConfigWidget::Pattern: + break; + } + } diff --git a/libs/ui/widgets/KoFillConfigWidget.h b/libs/ui/widgets/KoFillConfigWidget.h index e852335ac5..e2f2e3888b 100644 --- a/libs/ui/widgets/KoFillConfigWidget.h +++ b/libs/ui/widgets/KoFillConfigWidget.h @@ -1,106 +1,111 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2012 Jean-Nicolas Artaud * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FILLCONFIGWIDGET_H #define FILLCONFIGWIDGET_H #include "kritaui_export.h" #include #include #include #include class KoCanvasBase; class KoShapeBackground; class KoShape; /// A widget for configuring the fill of a shape class KRITAUI_EXPORT KoFillConfigWidget : public QWidget { Q_OBJECT enum StyleButton { None = 0, Solid, Gradient, Pattern }; public: explicit KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, QWidget *parent); ~KoFillConfigWidget() override; void setNoSelectionTrackingMode(bool value); /// Returns the list of the selected shape /// If you need to use only one shape, call currentShape() QList currentShapes(); + /// returns the selected index of the fill type + int selectedFillIndex(); + KoShapeStrokeSP createShapeStroke(); void activate(); void deactivate(); private Q_SLOTS: void styleButtonPressed(int buttonId); void noColorSelected(); /// apply color changes to the selected shape void colorChanged(); /// the pattern of the fill changed, apply the changes void patternChanged(QSharedPointer background); void shapeChanged(); void slotUpdateFillTitle(); void slotCanvasResourceChanged(int key, const QVariant &value); void slotSavePredefinedGradientClicked(); void activeGradientChanged(); void gradientResourceChanged(); void slotGradientTypeChanged(); void slotGradientRepeatChanged(); void slotProposeCurrentColorToResourceManager(); Q_SIGNALS: void sigFillChanged(); void sigInternalRequestColorToResourceManager(); private: /// update the widget with the KoShape background void updateWidget(KoShape *shape); void uploadNewGradientBackground(const QGradient *gradient); void setNewGradientBackgroundToShape(); void updateGradientSaveButtonAvailability(); void loadCurrentFillFromResourceServer(); + void updateWidgetComponentVisbility(); + class Private; Private * const d; }; #endif // FILLCONFIGWIDGET_H diff --git a/libs/ui/widgets/KoFillConfigWidget.ui b/libs/ui/widgets/KoFillConfigWidget.ui index 33a4b32d50..60c0e838ff 100644 --- a/libs/ui/widgets/KoFillConfigWidget.ui +++ b/libs/ui/widgets/KoFillConfigWidget.ui @@ -1,372 +1,371 @@ KoFillConfigWidget 0 0 - 779 - 697 + 314 + 251 - + + + 0 + 0 + + + - + 0 0 0 No fill None true true true 0 0 Solid color fill Solid true true 0 0 Gradient fill Gradient true true 0 0 Pattern fill Pattern true true Qt::Horizontal QSizePolicy::Preferred 5 5 TypeText Qt::Horizontal 10 5 - - - 2 - - - - - - - - - - - 0 - 0 - - - - ... - - - QToolButton::InstantPopup - - - true - - - Qt::NoArrow - - - - - - - ... - - - true - - - true - - - - - - - - - Qt::Vertical - - - - 20 - 10 - - - - - - - - + + + + + Color: + + + + + + + + 0 + 0 + + + + ... + + + QToolButton::InstantPopup + + + true + + + Qt::NoArrow + + + + + + + ... + + + true + + + true + + + + + + + + + + + Type: + + + + + + + + 0 + 0 + + + + + 0 + 30 + + - + + Linear + - - - Qt::Horizontal - - + + Radial + + + + + + + Repeat: + + + + + + + + 0 + 0 + + + + + 0 + 30 + + - - - - - Type: - - - - - - - - 0 - 0 - - - - - Linear - - - - - Radial - - - - - - - - Repeat: - - - - - - - - 0 - 0 - - - - - None - - - - - Repeat - - - - - Reflect - - - - - + + None + - - - - - - 0 - 0 - - - - ... - - - QToolButton::InstantPopup - - - true - - - Qt::NoArrow - - - - - - - ... - - - false - - - true - - - - + + Repeat + - - - Qt::Vertical - - - - 20 - 1 - - - + + Reflect + - - - + + + + + + + + + + Preset: + + + + + + + + 0 + 0 + + + + + 40 + 0 + + + + ... + + + QToolButton::InstantPopup + + + true + + + Qt::NoArrow + + + + + + + ... + + + false + + + true + + + + + + + + + Qt::Horizontal + - + + + + + + + + Qt::Vertical 20 - 13 + 40 KoColorPopupButton QToolButton
KoColorPopupButton.h
KisStopGradientEditor QWidget
kis_stopgradient_editor.h
1
diff --git a/libs/ui/widgets/KoStrokeConfigWidget.cpp b/libs/ui/widgets/KoStrokeConfigWidget.cpp index 6c7d6dfc04..f5b90c02b5 100644 --- a/libs/ui/widgets/KoStrokeConfigWidget.cpp +++ b/libs/ui/widgets/KoStrokeConfigWidget.cpp @@ -1,777 +1,800 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2002 Tomislav Lukman * Copyright (C) 2002-2003 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2007 Thomas Zander * Copyright (C) 2005-2006, 2011 Inge Wallin * Copyright (C) 2005-2008 Jan Hambrecht * Copyright (C) 2006 C. Boemann * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006 Laurent Montel * Copyright (C) 2007,2011 Thorsten Zachmann * Copyright (C) 2011 Jean-Nicolas Artaud * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "KoStrokeConfigWidget.h" // Qt #include -#include #include #include #include #include #include #include // KDE #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include "ui_KoStrokeConfigWidget.h" #include #include - +#include #include "kis_canvas_resource_provider.h" #include "kis_acyclic_signal_connector.h" // Krita #include "kis_double_parse_unit_spin_box.h" class CapNJoinMenu : public QMenu { public: CapNJoinMenu(QWidget *parent = 0); QSize sizeHint() const override; KisDoubleParseUnitSpinBox *miterLimit; QButtonGroup *capGroup; QButtonGroup *joinGroup; }; CapNJoinMenu::CapNJoinMenu(QWidget *parent) : QMenu(parent) { QGridLayout *mainLayout = new QGridLayout(); mainLayout->setMargin(2); // The cap group capGroup = new QButtonGroup(this); capGroup->setExclusive(true); QToolButton *button = 0; button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-butt")); button->setCheckable(true); button->setToolTip(i18n("Butt cap")); capGroup->addButton(button, Qt::FlatCap); mainLayout->addWidget(button, 2, 0); button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-round")); button->setCheckable(true); button->setToolTip(i18n("Round cap")); capGroup->addButton(button, Qt::RoundCap); mainLayout->addWidget(button, 2, 1); button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-square")); button->setCheckable(true); button->setToolTip(i18n("Square cap")); capGroup->addButton(button, Qt::SquareCap); mainLayout->addWidget(button, 2, 2, Qt::AlignLeft); // The join group joinGroup = new QButtonGroup(this); joinGroup->setExclusive(true); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-miter")); button->setCheckable(true); button->setToolTip(i18n("Miter join")); joinGroup->addButton(button, Qt::MiterJoin); mainLayout->addWidget(button, 3, 0); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-round")); button->setCheckable(true); button->setToolTip(i18n("Round join")); joinGroup->addButton(button, Qt::RoundJoin); mainLayout->addWidget(button, 3, 1); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-bevel")); button->setCheckable(true); button->setToolTip(i18n("Bevel join")); joinGroup->addButton(button, Qt::BevelJoin); mainLayout->addWidget(button, 3, 2, Qt::AlignLeft); // Miter limit // set min/max/step and value in points, then set actual unit miterLimit = new KisDoubleParseUnitSpinBox(this); miterLimit->setMinMaxStep(0.0, 1000.0, 0.5); miterLimit->setDecimals(2); miterLimit->setUnit(KoUnit(KoUnit::Point)); miterLimit->setToolTip(i18n("Miter limit")); mainLayout->addWidget(miterLimit, 4, 0, 1, 3); mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); setLayout(mainLayout); } QSize CapNJoinMenu::sizeHint() const { return layout()->sizeHint(); } class Q_DECL_HIDDEN KoStrokeConfigWidget::Private { public: Private() : canvas(0), active(true), allowLocalUnitManagement(true), fillConfigWidget(0), noSelectionTrackingMode(false) { } KoLineStyleSelector *lineStyle; KisDoubleParseUnitSpinBox *lineWidth; KoMarkerSelector *startMarkerSelector; KoMarkerSelector *midMarkerSelector; KoMarkerSelector *endMarkerSelector; - QToolButton *capNJoinButton; - CapNJoinMenu *capNJoinMenu; + CapNJoinMenu *capNJoinMenu; QWidget *spacer; KoCanvasBase *canvas; bool active; bool allowLocalUnitManagement; KoFillConfigWidget *fillConfigWidget; bool noSelectionTrackingMode; KisAcyclicSignalConnector shapeChangedAcyclicConnector; KisAcyclicSignalConnector resourceManagerAcyclicConnector; std::vector deactivationLocks; + + Ui_KoStrokeConfigWidget *ui; }; KoStrokeConfigWidget::KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget * parent) : QWidget(parent) , d(new Private()) { + // confure GUI + d->ui = new Ui_KoStrokeConfigWidget(); + d->ui->setupUi(this); + setObjectName("Stroke widget"); - QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(2); { // connect the canvas d->shapeChangedAcyclicConnector.connectBackwardVoid( canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); d->shapeChangedAcyclicConnector.connectBackwardVoid( canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(selectionChanged())); d->resourceManagerAcyclicConnector.connectBackwardResourcePair( canvas->resourceManager(), SIGNAL(canvasResourceChanged(int, const QVariant&)), this, SLOT(canvasResourceChanged(int, const QVariant &))); d->canvas = canvas; } - { - QHBoxLayout *markersLineLayout = new QHBoxLayout(); - QList emptyMarkers; + { - d->startMarkerSelector = new KoMarkerSelector(KoFlake::StartMarker, this); - d->startMarkerSelector->setToolTip(i18nc("@info:tooltip", "Start marker")); - d->startMarkerSelector->updateMarkers(emptyMarkers); - markersLineLayout->addWidget(d->startMarkerSelector); + d->fillConfigWidget = new KoFillConfigWidget(canvas, KoFlake::StrokeFill, this); + d->fillConfigWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + d->ui->fillConfigWidgetLayout->addWidget(d->fillConfigWidget); + connect(d->fillConfigWidget, SIGNAL(sigFillChanged()), SIGNAL(sigStrokeChanged())); + } - d->midMarkerSelector = new KoMarkerSelector(KoFlake::MidMarker, this); - d->midMarkerSelector->setToolTip(i18nc("@info:tooltip", "Node marker")); - d->midMarkerSelector->updateMarkers(emptyMarkers); - markersLineLayout->addWidget(d->midMarkerSelector); + d->ui->thicknessLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + d->ui->thicknessLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - d->endMarkerSelector = new KoMarkerSelector(KoFlake::EndMarker, this); - d->endMarkerSelector->setToolTip(i18nc("@info:tooltip", "End marker")); - d->endMarkerSelector->updateMarkers(emptyMarkers); - markersLineLayout->addWidget(d->endMarkerSelector); + // set min/max/step and value in points, then set actual unit + d->ui->lineWidth->setMinMaxStep(0.0, 1000.0, 0.5); + d->ui->lineWidth->setDecimals(2); + d->ui->lineWidth->setUnit(KoUnit(KoUnit::Point)); + d->ui->lineWidth->setToolTip(i18n("Set line width of actual selection")); - mainLayout->addLayout(markersLineLayout); - } + d->ui->capNJoinButton->setMinimumHeight(25); + d->capNJoinMenu = new CapNJoinMenu(this); + d->ui->capNJoinButton->setMenu(d->capNJoinMenu); + d->ui->capNJoinButton->setText("..."); + d->ui->capNJoinButton->setPopupMode(QToolButton::InstantPopup); { - QHBoxLayout *styleLineLayout = new QHBoxLayout(); - // Line style - d->lineStyle = new KoLineStyleSelector(this); - d->lineStyle->setToolTip(i18nc("@info:tooltip", "Line style")); - d->lineStyle->setMinimumWidth(70); - d->lineStyle->setLineStyle(Qt::SolidLine, QVector()); - styleLineLayout->addWidget(d->lineStyle); + d->ui->strokeStyleLabel->setText(i18n("Line Style:")); + d->ui->strokeStyleLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - mainLayout->addLayout(styleLineLayout); + d->ui->lineStyle->setToolTip(i18nc("@info:tooltip", "Line style")); + d->ui->lineStyle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector()); } - QHBoxLayout *widthLineLayout = new QHBoxLayout(); - // Line width - QLabel *l = new QLabel(this); - l->setText(i18n("Thickness:")); - l->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - widthLineLayout->addWidget(l); + { + QList emptyMarkers; - // set min/max/step and value in points, then set actual unit - d->lineWidth = new KisDoubleParseUnitSpinBox(this); - d->lineWidth->setMinMaxStep(0.0, 1000.0, 0.5); - d->lineWidth->setDecimals(2); - d->lineWidth->setUnit(KoUnit(KoUnit::Point)); - d->lineWidth->setToolTip(i18n("Set line width of actual selection")); - widthLineLayout->addWidget(d->lineWidth); - - d->capNJoinButton = new QToolButton(this); - d->capNJoinButton->setMinimumHeight(25); - d->capNJoinMenu = new CapNJoinMenu(this); - d->capNJoinButton->setMenu(d->capNJoinMenu); - d->capNJoinButton->setText("..."); - d->capNJoinButton->setPopupMode(QToolButton::InstantPopup); - widthLineLayout->addWidget(d->capNJoinButton); + d->startMarkerSelector = new KoMarkerSelector(KoFlake::StartMarker, this); + d->startMarkerSelector->setToolTip(i18nc("@info:tooltip", "Start marker")); + d->startMarkerSelector->updateMarkers(emptyMarkers); + d->startMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); + d->ui->markerLayout->addWidget(d->startMarkerSelector); - mainLayout->addLayout(widthLineLayout); - { // add separator line - QFrame* line = new QFrame(); - line->setFrameShape(QFrame::HLine); - mainLayout->addWidget(line); - } + d->midMarkerSelector = new KoMarkerSelector(KoFlake::MidMarker, this); + d->midMarkerSelector->setToolTip(i18nc("@info:tooltip", "Node marker")); + d->midMarkerSelector->updateMarkers(emptyMarkers); + d->midMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); + d->ui->markerLayout->addWidget(d->midMarkerSelector); - { - d->fillConfigWidget = new KoFillConfigWidget(canvas, KoFlake::StrokeFill, this); - mainLayout->addWidget(d->fillConfigWidget); - connect(d->fillConfigWidget, SIGNAL(sigFillChanged()), SIGNAL(sigStrokeChanged())); - d->fillConfigWidget->layout()->setMargin(0); + d->endMarkerSelector = new KoMarkerSelector(KoFlake::EndMarker, this); + d->endMarkerSelector->setToolTip(i18nc("@info:tooltip", "End marker")); + d->endMarkerSelector->updateMarkers(emptyMarkers); + d->endMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); + + d->ui->markerLayout->addWidget(d->endMarkerSelector); } // Spacer d->spacer = new QWidget(); d->spacer->setObjectName("SpecialSpacer"); - mainLayout->addWidget(d->spacer); - connect(d->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyDashStyleChanges())); - connect(d->lineWidth, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyLineWidthChanges())); + d->ui->markerLayout->addWidget(d->spacer); + + connect(d->ui->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyDashStyleChanges())); + connect(d->ui->lineWidth, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyLineWidthChanges())); connect(d->capNJoinMenu->capGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges())); connect(d->capNJoinMenu->joinGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges())); connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyJoinCapChanges())); { // Map the marker signals correclty QSignalMapper *mapper = new QSignalMapper(this); connect(mapper, SIGNAL(mapped(int)), SLOT(applyMarkerChanges(int))); connect(d->startMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); connect(d->midMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); connect(d->endMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); mapper->setMapping(d->startMarkerSelector, KoFlake::StartMarker); mapper->setMapping(d->midMarkerSelector, KoFlake::MidMarker); mapper->setMapping(d->endMarkerSelector, KoFlake::EndMarker); } KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager(); if (resourceManager) { KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value(); if (collection) { updateMarkers(collection->markers()); } } selectionChanged(); d->fillConfigWidget->activate(); deactivate(); } KoStrokeConfigWidget::~KoStrokeConfigWidget() { delete d; } void KoStrokeConfigWidget::setNoSelectionTrackingMode(bool value) { d->fillConfigWidget->setNoSelectionTrackingMode(value); d->noSelectionTrackingMode = value; if (!d->noSelectionTrackingMode) { selectionChanged(); } } // ---------------------------------------------------------------- // getters and setters Qt::PenStyle KoStrokeConfigWidget::lineStyle() const { - return d->lineStyle->lineStyle(); + return d->ui->lineStyle->lineStyle(); } QVector KoStrokeConfigWidget::lineDashes() const { - return d->lineStyle->lineDashes(); + return d->ui->lineStyle->lineDashes(); } qreal KoStrokeConfigWidget::lineWidth() const { - return d->lineWidth->value(); + return d->ui->lineWidth->value(); } qreal KoStrokeConfigWidget::miterLimit() const { return d->capNJoinMenu->miterLimit->value(); } KoMarker *KoStrokeConfigWidget::startMarker() const { return d->startMarkerSelector->marker(); } KoMarker *KoStrokeConfigWidget::endMarker() const { return d->endMarkerSelector->marker(); } Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const { return static_cast(d->capNJoinMenu->capGroup->checkedId()); } Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const { return static_cast(d->capNJoinMenu->joinGroup->checkedId()); } KoShapeStrokeSP KoStrokeConfigWidget::createShapeStroke() { KoShapeStrokeSP stroke(d->fillConfigWidget->createShapeStroke()); stroke->setLineWidth(lineWidth()); stroke->setCapStyle(capStyle()); stroke->setJoinStyle(joinStyle()); stroke->setMiterLimit(miterLimit()); stroke->setLineStyle(lineStyle(), lineDashes()); return stroke; } // ---------------------------------------------------------------- // Other public functions void KoStrokeConfigWidget::updateStyleControlsAvailability(bool enabled) { - d->lineWidth->setEnabled(enabled); + d->ui->lineWidth->setEnabled(enabled); d->capNJoinMenu->setEnabled(enabled); - d->lineStyle->setEnabled(enabled); + d->ui->lineStyle->setEnabled(enabled); d->startMarkerSelector->setEnabled(enabled); d->midMarkerSelector->setEnabled(enabled); d->endMarkerSelector->setEnabled(enabled); } void KoStrokeConfigWidget::setUnit(const KoUnit &unit, KoShape *representativeShape) { if (!d->allowLocalUnitManagement) { return; //the unit management is completly transfered to the unitManagers. } blockChildSignals(true); /** * KoStrokeShape knows nothing about the transformations applied * to the shape, which doesn't prevent the shape to apply them and * display the stroke differently. So just take that into account * and show the user correct values using the multiplier in KoUnit. */ KoUnit newUnit(unit); if (representativeShape) { newUnit.adjustByPixelTransform(representativeShape->absoluteTransformation(0)); } - d->lineWidth->setUnit(newUnit); + d->ui->lineWidth->setUnit(newUnit); d->capNJoinMenu->miterLimit->setUnit(newUnit); - d->lineWidth->setLineStep(1.0); + d->ui->lineWidth->setLineStep(1.0); d->capNJoinMenu->miterLimit->setLineStep(1.0); blockChildSignals(false); } void KoStrokeConfigWidget::setUnitManagers(KisSpinBoxUnitManager* managerLineWidth, KisSpinBoxUnitManager *managerMitterLimit) { blockChildSignals(true); d->allowLocalUnitManagement = false; - d->lineWidth->setUnitManager(managerLineWidth); + d->ui->lineWidth->setUnitManager(managerLineWidth); d->capNJoinMenu->miterLimit->setUnitManager(managerMitterLimit); blockChildSignals(false); } void KoStrokeConfigWidget::updateMarkers(const QList &markers) { d->startMarkerSelector->updateMarkers(markers); d->midMarkerSelector->updateMarkers(markers); d->endMarkerSelector->updateMarkers(markers); } void KoStrokeConfigWidget::activate() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty()); d->deactivationLocks.clear(); d->fillConfigWidget->activate(); if (!d->noSelectionTrackingMode) { selectionChanged(); } else { loadCurrentStrokeFillFromResourceServer(); } } void KoStrokeConfigWidget::deactivate() { KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty()); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector)); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector)); d->fillConfigWidget->deactivate(); } void KoStrokeConfigWidget::blockChildSignals(bool block) { - d->lineWidth->blockSignals(block); + d->ui->lineWidth->blockSignals(block); d->capNJoinMenu->capGroup->blockSignals(block); d->capNJoinMenu->joinGroup->blockSignals(block); d->capNJoinMenu->miterLimit->blockSignals(block); - d->lineStyle->blockSignals(block); + d->ui->lineStyle->blockSignals(block); d->startMarkerSelector->blockSignals(block); d->midMarkerSelector->blockSignals(block); d->endMarkerSelector->blockSignals(block); } void KoStrokeConfigWidget::setActive(bool active) { d->active = active; } //------------------------ template auto applyChangeToStrokes(KoCanvasBase *canvas, ModifyFunction modifyFunction) -> decltype(modifyFunction(KoShapeStrokeSP()), void()) { KoSelection *selection = canvas->selectedShapesProxy()->selection(); if (!selection) return; QList shapes = selection->selectedEditableShapes(); KUndo2Command *command = KoFlake::modifyShapesStrokes(shapes, modifyFunction); if (command) { canvas->addCommand(command); } } void KoStrokeConfigWidget::applyDashStyleChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setLineStyle(lineStyle(), lineDashes()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyLineWidthChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setLineWidth(lineWidth()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyJoinCapChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setCapStyle(static_cast(d->capNJoinMenu->capGroup->checkedId())); stroke->setJoinStyle(static_cast(d->capNJoinMenu->joinGroup->checkedId())); stroke->setMiterLimit(miterLimit()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyMarkerChanges(int rawPosition) { KoSelection *selection = d->canvas->selectedShapesProxy()->selection(); if (!selection) { emit sigStrokeChanged(); return; } QList shapes = selection->selectedEditableShapes(); QList pathShapes; Q_FOREACH (KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { pathShapes << pathShape; } } if (pathShapes.isEmpty()) { emit sigStrokeChanged(); return; } KoFlake::MarkerPosition position = KoFlake::MarkerPosition(rawPosition); QScopedPointer marker; switch (position) { case KoFlake::StartMarker: if (d->startMarkerSelector->marker()) { marker.reset(new KoMarker(*d->startMarkerSelector->marker())); } break; case KoFlake::MidMarker: if (d->midMarkerSelector->marker()) { marker.reset(new KoMarker(*d->midMarkerSelector->marker())); } break; case KoFlake::EndMarker: if (d->endMarkerSelector->marker()) { marker.reset(new KoMarker(*d->endMarkerSelector->marker())); } break; } KUndo2Command* command = new KoPathShapeMarkerCommand(pathShapes, marker.take(), position); d->canvas->addCommand(command); emit sigStrokeChanged(); } // ---------------------------------------------------------------- struct CheckShapeStrokeStyleBasePolicy { typedef KoShapeStrokeSP PointerType; static PointerType getProperty(KoShape *shape) { return qSharedPointerDynamicCast(shape->stroke()); } }; struct CheckShapeStrokeDashesPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->lineStyle() == p2->lineStyle() && p1->lineDashes() == p2->lineDashes() && p1->dashOffset() == p2->dashOffset(); } }; struct CheckShapeStrokeCapJoinPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->capStyle() == p2->capStyle() && p1->joinStyle() == p2->joinStyle() && p1->miterLimit() == p2->miterLimit(); } }; struct CheckShapeStrokeWidthPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->lineWidth() == p2->lineWidth(); } }; struct CheckShapeMarkerPolicy { CheckShapeMarkerPolicy(KoFlake::MarkerPosition position) : m_position(position) { } typedef KoMarker* PointerType; PointerType getProperty(KoShape *shape) const { KoPathShape *pathShape = dynamic_cast(shape); return pathShape ? pathShape->marker(m_position) : 0; } bool compareTo(PointerType p1, PointerType p2) const { if ((!p1 || !p2) && p1 != p2) return false; if (!p1 && p1 == p2) return true; return p1 == p2 || *p1 == *p2; } KoFlake::MarkerPosition m_position; }; void KoStrokeConfigWidget::selectionChanged() { + KoSelection *selection = d->canvas->selectedShapesProxy()->selection(); if (!selection) return; + QList shapes = selection->selectedEditableShapes(); KoShape *shape = !shapes.isEmpty() ? shapes.first() : 0; + const KoShapeStrokeSP stroke = shape ? qSharedPointerDynamicCast(shape->stroke()) : KoShapeStrokeSP(); // setUnit uses blockChildSignals() so take care not to use it inside the block setUnit(d->canvas->unit(), shape); blockChildSignals(true); // line width if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { - d->lineWidth->changeValue(stroke->lineWidth()); + d->ui->lineWidth->changeValue(stroke->lineWidth()); } else { - d->lineWidth->changeValue(0); + d->ui->lineWidth->changeValue(0); } + // caps & joins if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { Qt::PenCapStyle capStyle = stroke->capStyle() >= 0 ? stroke->capStyle() : Qt::FlatCap; Qt::PenJoinStyle joinStyle = stroke->joinStyle() >= 0 ? stroke->joinStyle() : Qt::MiterJoin; { QAbstractButton *button = d->capNJoinMenu->capGroup->button(capStyle); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } { QAbstractButton *button = d->capNJoinMenu->joinGroup->button(joinStyle); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } d->capNJoinMenu->miterLimit->changeValue(stroke->miterLimit()); d->capNJoinMenu->miterLimit->setEnabled(joinStyle == Qt::MiterJoin); } else { d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true); d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true); d->capNJoinMenu->miterLimit->changeValue(0.0); d->capNJoinMenu->miterLimit->setEnabled(true); } + // dashes style if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { - d->lineStyle->setLineStyle(stroke->lineStyle(), stroke->lineDashes()); + d->ui->lineStyle->setLineStyle(stroke->lineStyle(), stroke->lineDashes()); } else { - d->lineStyle->setLineStyle(Qt::SolidLine, QVector()); + d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector()); } // markers KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::StartMarker))) { d->startMarkerSelector->setMarker(pathShape->marker(KoFlake::StartMarker)); } if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::MidMarker))) { d->midMarkerSelector->setMarker(pathShape->marker(KoFlake::MidMarker)); } if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::EndMarker))) { d->endMarkerSelector->setMarker(pathShape->marker(KoFlake::EndMarker)); } } + const bool lineOptionsVisible = d->fillConfigWidget->selectedFillIndex() == 0 ? false : true; + + // This switch statement is to help the tab widget "pages" to be closer to the correct size + // if we don't do this the internal widgets get rendered, then the tab page has to get resized to + // fill up the space, then the internal widgets have to resize yet again...causing flicker + switch(d->fillConfigWidget->selectedFillIndex()) { + case 0: // no fill + this->setMinimumHeight(130); + break; + case 1: // solid fill + this->setMinimumHeight(200); + break; + case 2: // gradient fill + this->setMinimumHeight(350); + case 3: // pattern fill + break; + } + + + d->ui->thicknessLineBreak->setVisible(lineOptionsVisible); + d->ui->lineWidth->setVisible(lineOptionsVisible); + d->ui->capNJoinButton->setVisible(lineOptionsVisible); + d->ui->lineStyle->setVisible(lineOptionsVisible); + d->startMarkerSelector->setVisible(lineOptionsVisible); + d->midMarkerSelector->setVisible(lineOptionsVisible); + d->endMarkerSelector->setVisible(lineOptionsVisible); + d->ui->thicknessLabel->setVisible(lineOptionsVisible); + d->ui->strokeStyleLabel->setVisible(lineOptionsVisible); + + + blockChildSignals(false); updateStyleControlsAvailability(!shapes.isEmpty()); + } void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value) { switch (key) { case KoCanvasResourceManager::Unit: // we request the whole selection to reload because the // unit of the stroke width depends on the selected shape selectionChanged(); break; case KisCanvasResourceProvider::Size: if (d->noSelectionTrackingMode) { - d->lineWidth->changeValue(d->canvas->unit().fromUserValue(value.toReal())); + d->ui->lineWidth->changeValue(d->canvas->unit().fromUserValue(value.toReal())); } break; } } void KoStrokeConfigWidget::loadCurrentStrokeFillFromResourceServer() { if (d->canvas) { const QVariant value = d->canvas->resourceManager()->resource(KisCanvasResourceProvider::Size); canvasResourceChanged(KisCanvasResourceProvider::Size, value); updateStyleControlsAvailability(true); emit sigStrokeChanged(); } } diff --git a/libs/ui/widgets/KoStrokeConfigWidget.h b/libs/ui/widgets/KoStrokeConfigWidget.h index 28aa31a7ef..f41e68dbf5 100644 --- a/libs/ui/widgets/KoStrokeConfigWidget.h +++ b/libs/ui/widgets/KoStrokeConfigWidget.h @@ -1,114 +1,118 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2002 Tomislav Lukman * Copyright (C) 2002 Rob Buis * Copyright (C) 2004 Laurent Montel * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005 Inge Wallin * Copyright (C) 2005, 2011 Thomas Zander * Copyright (C) 2005-2008 Jan Hambrecht * Copyright (C) 2006 C. Boemann * Copyright (C) 2011 Jean-Nicolas Artaud * Copyright (C) 2011 Thorsten Zachmann * * 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 STROKECONFIGWIDGET_H #define STROKECONFIGWIDGET_H #include "kritaui_export.h" #include +#include #include #include + class KoUnit; class KoShapeStrokeModel; class KoShapeStroke; class KoMarker; class KoCanvasBase; class KoShapeStroke; - class KisSpinBoxUnitManager; /// A widget for configuring the stroke of a shape class KRITAUI_EXPORT KoStrokeConfigWidget : public QWidget { Q_OBJECT public: explicit KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget *parent); ~KoStrokeConfigWidget() override; void setNoSelectionTrackingMode(bool value); // Getters Qt::PenStyle lineStyle() const; QVector lineDashes() const; qreal lineWidth() const; QColor color() const; qreal miterLimit() const; KoMarker *startMarker() const; KoMarker *endMarker() const; Qt::PenCapStyle capStyle() const; Qt::PenJoinStyle joinStyle() const; + QLabel *thicknessLabel; + QLabel *strokeStyleLabel; + QFrame* separatorLine; /** * Creates KoShapeStroke object filled with the options * configured by the widget. The caller is in charge of * deletion of the returned object */ KoShapeStrokeSP createShapeStroke(); void setActive(bool active); void updateStyleControlsAvailability(bool enabled); void setUnitManagers(KisSpinBoxUnitManager* managerLineWidth, KisSpinBoxUnitManager* managerMitterLimit); void activate(); void deactivate(); private Q_SLOTS: void updateMarkers(const QList &markers); void canvasResourceChanged(int key, const QVariant &value); /// selection has changed void selectionChanged(); /// apply line changes to the selected shapes void applyDashStyleChanges(); void applyLineWidthChanges(); void applyJoinCapChanges(); /// apply marker changes to the selected shape void applyMarkerChanges(int rawPosition); Q_SIGNALS: void sigStrokeChanged(); private: void setUnit(const KoUnit &unit, KoShape *representativeShape); void blockChildSignals(bool block); void loadCurrentStrokeFillFromResourceServer(); private: class Private; Private * const d; }; #endif // SHADOWCONFIGWIDGET_H diff --git a/libs/ui/widgets/KoStrokeConfigWidget.ui b/libs/ui/widgets/KoStrokeConfigWidget.ui new file mode 100644 index 0000000000..e94a0109d6 --- /dev/null +++ b/libs/ui/widgets/KoStrokeConfigWidget.ui @@ -0,0 +1,125 @@ + + + KoStrokeConfigWidget + + + + 0 + 0 + 226 + 100 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + + + + + 9 + + + 9 + + + + + Thickness: + + + + + + + + + + ... + + + + + + + + + 9 + + + 9 + + + + + Line Style: + + + + + + + + + + + + 9 + + + 9 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KisDoubleParseUnitSpinBox + QWidget +
kis_double_parse_unit_spin_box.h
+
+ + KoLineStyleSelector + QWidget +
KoLineStyleSelector.h
+
+
+ + +
diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc index dee04ed433..1c30cad434 100644 --- a/libs/ui/widgets/kis_advanced_color_space_selector.cc +++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc @@ -1,796 +1,793 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselectoradvanced.h" #include struct KisAdvancedColorSpaceSelector::Private { Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector; QString knsrcFile; }; KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption) : QDialog(parent) , d(new Private) { setWindowTitle(caption); d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbDepths(const KoID &))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(colorSpaceChanged())); connect(this, SIGNAL(selectionChanged(bool)), this, SLOT(fillDescription())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept())); connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject())); fillLstProfiles(); } KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisAdvancedColorSpaceSelector::fillLstProfiles() { d->colorSpaceSelector->lstProfile->blockSignals(true); - QString s = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); + const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); + const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->lstProfile->clear(); - const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s); - if (csf == 0) return;//TODO: make this give better feedback. - - QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf); + QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } qSort(profileNames); QListWidgetItem *defaultProfile = new QListWidgetItem; - defaultProfile->setText(csf->defaultProfile() + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); + defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); Q_FOREACH (QString stringName, profileNames) { - if (stringName==csf->defaultProfile()) { + if (stringName == defaultProfileName) { d->colorSpaceSelector->lstProfile->addItem(defaultProfile); } else { d->colorSpaceSelector->lstProfile->addItem(stringName); } } d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile); d->colorSpaceSelector->lstProfile->blockSignals(false); colorSpaceChanged(); } void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); QList sortedDepths; if (depths.contains(Integer8BitsColorDepthID)) { sortedDepths << Integer8BitsColorDepthID; } if (depths.contains(Integer16BitsColorDepthID)) { sortedDepths << Integer16BitsColorDepthID; } if (depths.contains(Float16BitsColorDepthID)) { sortedDepths << Float16BitsColorDepthID; } if (depths.contains(Float32BitsColorDepthID)) { sortedDepths << Float32BitsColorDepthID; } if (depths.contains(Float64BitsColorDepthID)) { sortedDepths << Float64BitsColorDepthID; } d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths); if (sortedDepths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } void KisAdvancedColorSpaceSelector::fillDescription() { QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A"); QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants."); QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found"); QString whatIsColorant = i18n("Colorant in d50-adapted xyY."); //set colorants - QString s = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); - const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s); - if (csf == 0) return; - QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf); - if (profileList.isEmpty()==false) { + const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); + QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); + + if (!profileList.isEmpty()) { profileName = currentColorSpace()->profile()->name(); if (currentColorSpace()->profile()->hasColorants()){ QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); //QString text = currentColorSpace()->profile()->info() + " =" + d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip("
"+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"
"+ i18n("Red:") +""+QString::number(colorants[0], 'f', 4) + "" + QString::number(colorants[1], 'f', 4) + "" + QString::number(colorants[2], 'f', 4)+"
"+ i18n("Green:")+""+QString::number(colorants[3], 'f', 4) + "" + QString::number(colorants[4], 'f', 4) + "" + QString::number(colorants[5], 'f', 4)+"
"+ i18n("Blue:") +""+QString::number(colorants[6], 'f', 4) + "" + QString::number(colorants[7], 'f', 4) + "" + QString::number(colorants[8], 'f', 4)+"
"); } else { QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } } else { d->colorSpaceSelector->lblXYZ_W->setText(notApplicable); d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } //set TRC QVector estimatedTRC(3); QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: "); QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC"); QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC."); QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id(); if (profileList.isEmpty()) { d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false); d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } else if (currentModelStr == "RGBA") { QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); if (currentColorSpace()->profile()->hasColorants()){ d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } else { colorants.fill(0.0); d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF redcurve; QPolygonF greencurve; QPolygonF bluecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
"+estimatedCurve+""); } } else if (currentModelStr == "GRAYA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
"+estimatedCurve+""); } } else if (currentModelStr == "CMYKA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; QPolygonF cyancurve; QPolygonF magentacurve; QPolygonF yellowcurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19); d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve); } d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK.")); } else if (currentModelStr == "XYZA") { QString estimatedCurve = " Estimated curve: "; estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+""); } else if (currentModelStr == "LABA") { estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setLABData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
"+estimatedCurve+""); } else if (currentModelStr == "YCbCrA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb.")); } d->colorSpaceSelector->textProfileDescription->clear(); if (profileList.isEmpty()==false) { d->colorSpaceSelector->textProfileDescription->append("

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

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

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

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

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

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

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

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

" + profileName + "

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Extra notes on profiles by Elle Stone:

" "

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

")); } } d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start); } QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) { QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4)); //A (0.451170, 0.40594) (2856K)(tungsten) if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) && (whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){ name="A"; return name; } //B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete) //C (0.31039, 0.31905) (6774K) (avarage/north sky daylight)(obsolete) //D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant) if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) && (whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){ name="D50"; return name; } //D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight) if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) && (whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){ name="D55"; return name; } //D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default) if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) && (whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){ name="D60"; return name; } //D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default) //Elle's are old school with 0.3127 and 0.3289 if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) && (whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){ name="D65"; return name; } //D75 (0.29968, 0.31740) (7504K) (North sky Daylight) if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) && (whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){ name="D75"; return name; } //E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default) if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) && (whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){ name="E"; return name; } //The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.// //F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent) //F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent) //F3 (0.41761, 0.38324) (3450K) (White Florescent) //F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent) //F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent) //F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent) //F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator) //F8 (0.34902, 0.35939) (5000K) (D50 simulator) //F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent) //F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50) //F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40) //F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30) return name; } const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace() { QString check = ""; if (d->colorSpaceSelector->lstProfile->currentItem()) { check = d->colorSpaceSelector->lstProfile->currentItem()->text(); } else if (d->colorSpaceSelector->lstProfile->item(0)) { check = d->colorSpaceSelector->lstProfile->item(0)->text(); } return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), check); } void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillLstProfiles(); fillCmbDepths(id); } void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillLstProfiles(); } void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name) { QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith); d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0)); } void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisAdvancedColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->lstProfile->count() != 0; emit(selectionChanged(valid)); if (valid) { emit colorSpaceChanged(currentColorSpace()); } } void KisAdvancedColorSpaceSelector::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillLstProfiles(); } diff --git a/libs/ui/widgets/kis_cie_tongue_widget.cpp b/libs/ui/widgets/kis_cie_tongue_widget.cpp index 9afa11441f..b255f47489 100644 --- a/libs/ui/widgets/kis_cie_tongue_widget.cpp +++ b/libs/ui/widgets/kis_cie_tongue_widget.cpp @@ -1,737 +1,735 @@ /* * Copyright (C) 2015 by Wolthera van Hövell tot Westerflier * * Based on the Digikam CIE Tongue widget * Copyright (C) 2006-2013 by Gilles Caulier * * Any source code are inspired from lprof project and * Copyright (C) 1998-2001 Marti Maria * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * 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. **/ /** The following table gives the CIE color matching functions \f$\bar{x}(\lambda)\f$, \f$\bar{y}(\lambda)\f$, and \f$\bar{z}(\lambda)\f$, for wavelengths \f$\lambda\f$ at 5 nanometer increments from 380 nm through 780 nm. This table is used in conjunction with Planck's law for the energy spectrum of a black body at a given temperature to plot the black body curve on the CIE chart. The following table gives the spectral chromaticity co-ordinates \f$x(\lambda)\f$ and \f$y(\lambda)\f$ for wavelengths in 5 nanometer increments from 380 nm through 780 nm. These coordinates represent the position in the CIE x-y space of pure spectral colors of the given wavelength, and thus define the outline of the CIE "tongue" diagram. */ #include #include #include #include #include #include #include #include #include #include #include #include "kis_cie_tongue_widget.h" static const double spectral_chromaticity[81][3] = { { 0.1741, 0.0050 }, // 380 nm { 0.1740, 0.0050 }, { 0.1738, 0.0049 }, { 0.1736, 0.0049 }, { 0.1733, 0.0048 }, { 0.1730, 0.0048 }, { 0.1726, 0.0048 }, { 0.1721, 0.0048 }, { 0.1714, 0.0051 }, { 0.1703, 0.0058 }, { 0.1689, 0.0069 }, { 0.1669, 0.0086 }, { 0.1644, 0.0109 }, { 0.1611, 0.0138 }, { 0.1566, 0.0177 }, { 0.1510, 0.0227 }, { 0.1440, 0.0297 }, { 0.1355, 0.0399 }, { 0.1241, 0.0578 }, { 0.1096, 0.0868 }, { 0.0913, 0.1327 }, { 0.0687, 0.2007 }, { 0.0454, 0.2950 }, { 0.0235, 0.4127 }, { 0.0082, 0.5384 }, { 0.0039, 0.6548 }, { 0.0139, 0.7502 }, { 0.0389, 0.8120 }, { 0.0743, 0.8338 }, { 0.1142, 0.8262 }, { 0.1547, 0.8059 }, { 0.1929, 0.7816 }, { 0.2296, 0.7543 }, { 0.2658, 0.7243 }, { 0.3016, 0.6923 }, { 0.3373, 0.6589 }, { 0.3731, 0.6245 }, { 0.4087, 0.5896 }, { 0.4441, 0.5547 }, { 0.4788, 0.5202 }, { 0.5125, 0.4866 }, { 0.5448, 0.4544 }, { 0.5752, 0.4242 }, { 0.6029, 0.3965 }, { 0.6270, 0.3725 }, { 0.6482, 0.3514 }, { 0.6658, 0.3340 }, { 0.6801, 0.3197 }, { 0.6915, 0.3083 }, { 0.7006, 0.2993 }, { 0.7079, 0.2920 }, { 0.7140, 0.2859 }, { 0.7190, 0.2809 }, { 0.7230, 0.2770 }, { 0.7260, 0.2740 }, { 0.7283, 0.2717 }, { 0.7300, 0.2700 }, { 0.7311, 0.2689 }, { 0.7320, 0.2680 }, { 0.7327, 0.2673 }, { 0.7334, 0.2666 }, { 0.7340, 0.2660 }, { 0.7344, 0.2656 }, { 0.7346, 0.2654 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 } // 780 nm }; class Q_DECL_HIDDEN KisCIETongueWidget::Private { public: Private() : profileDataAvailable(false), needUpdatePixmap(false), cieTongueNeedsUpdate(true), uncalibratedColor(false), xBias(0), yBias(0), pxcols(0), pxrows(0), progressCount(0), gridside(0), progressTimer(0), Primaries(9), whitePoint(3) { progressPix = KPixmapSequence("process-working", KisIconUtils::SizeSmallMedium); } bool profileDataAvailable; bool needUpdatePixmap; bool cieTongueNeedsUpdate; bool uncalibratedColor; int xBias; int yBias; int pxcols; int pxrows; int progressCount; // Position of animation during loading/calculation. double gridside; QPainter painter; QTimer* progressTimer; QPixmap pixmap; QPixmap cietongue; QPixmap gamutMap; KPixmapSequence progressPix; QVector Primaries; QVector whitePoint; QPolygonF gamut; model colorModel; }; KisCIETongueWidget::KisCIETongueWidget(QWidget *parent) : QWidget(parent), d(new Private) { d->progressTimer = new QTimer(this); setAttribute(Qt::WA_DeleteOnClose); d->Primaries.resize(9); d->Primaries.fill(0.0); d->whitePoint.resize(3); d->whitePoint<<0.34773<<0.35952<<1.0; d->gamut = QPolygonF(); connect(d->progressTimer, SIGNAL(timeout()), this, SLOT(slotProgressTimerDone())); } KisCIETongueWidget::~KisCIETongueWidget() { delete d; } int KisCIETongueWidget::grids(double val) const { return (int) floor(val * d->gridside + 0.5); } void KisCIETongueWidget::setProfileData(QVector p, QVector w, bool profileData) { d->profileDataAvailable = profileData; if (profileData){ d->Primaries= p; d->whitePoint = w; d->needUpdatePixmap = true; } else { return; } } void KisCIETongueWidget::setGamut(QPolygonF gamut) { d->gamut=gamut; } void KisCIETongueWidget::setRGBData(QVector whitepoint, QVector colorants) { if (colorants.size()==9){ d->Primaries= colorants; d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::RGBA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setCMYKData(QVector whitepoint) { if (whitepoint.size()==3){ //d->Primaries= colorants; d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::CMYKA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setXYZData(QVector whitepoint) { if (whitepoint.size()==3){ d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::XYZA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setGrayData(QVector whitepoint) { if (whitepoint.size()==3){ d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::GRAYA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setLABData(QVector whitepoint) { if (whitepoint.size()==3){ d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::LABA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setYCbCrData(QVector whitepoint) { if (whitepoint.size()==3){ d->whitePoint = whitepoint; d->needUpdatePixmap = true; d->colorModel = KisCIETongueWidget::YCbCrA; d->profileDataAvailable = true; } else { return; } } void KisCIETongueWidget::setProfileDataAvailable(bool dataAvailable) { d->profileDataAvailable = dataAvailable; } void KisCIETongueWidget::mapPoint(int& icx, int& icy, QPointF xy) { icx = (int) floor((xy.x() * (d->pxcols - 1)) + .5); icy = (int) floor(((d->pxrows - 1) - xy.y() * (d->pxrows - 1)) + .5); } void KisCIETongueWidget::biasedLine(int x1, int y1, int x2, int y2) { d->painter.drawLine(x1 + d->xBias, y1, x2 + d->xBias, y2); } void KisCIETongueWidget::biasedText(int x, int y, const QString& txt) { d->painter.drawText(QPoint(d->xBias + x, y), txt); } QRgb KisCIETongueWidget::colorByCoord(double x, double y) { // Get xyz components scaled from coordinates double cx = ((double) x) / (d->pxcols - 1); double cy = 1.0 - ((double) y) / (d->pxrows - 1); double cz = 1.0 - cx - cy; // Project xyz to XYZ space. Note that in this // particular case we are substituting XYZ with xyz //Need to use KoColor here. - QString space = KoColorSpaceRegistry::instance()->colorSpaceId("XYZA", "U8"); - QString profile = KoColorSpaceRegistry::instance()->colorSpaceFactory(space)->defaultProfile(); - const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "U8", profile); + const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "U8"); quint8 data[4]; data[0]= cx*255; data[1]= cy*255; data[2]= cz*255; data[3]= 1.0*255; KoColor colXYZ(data, xyzColorSpace); QColor colRGB = colXYZ.toQColor(); return qRgb(colRGB.red(), colRGB.green(), colRGB.blue()); } void KisCIETongueWidget::outlineTongue() { int lx=0, ly=0; int fx=0, fy=0; for (int x = 380; x <= 700; x += 5) { int ix = (x - 380) / 5; QPointF * p = new QPointF(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]); int icx, icy; mapPoint(icx, icy, * p); if (x > 380) { biasedLine(lx, ly, icx, icy); } else { fx = icx; fy = icy; } lx = icx; ly = icy; } biasedLine(lx, ly, fx, fy); } void KisCIETongueWidget::fillTongue() { QImage Img = d->cietongue.toImage(); int x; for (int y = 0; y < d->pxrows; ++y) { int xe = 0; // Find horizontal extents of tongue on this line. for (x = 0; x < d->pxcols; ++x) { if (QColor(Img.pixel(x + d->xBias, y)) != QColor(Qt::black)) { for (xe = d->pxcols - 1; xe >= x; --xe) { if (QColor(Img.pixel(xe + d->xBias, y)) != QColor(Qt::black)) { break; } } break; } } if (x < d->pxcols) { for ( ; x <= xe; ++x) { QRgb Color = colorByCoord(x, y); Img.setPixel(x + d->xBias, y, Color); } } } d->cietongue = QPixmap::fromImage(Img, Qt::AvoidDither); } void KisCIETongueWidget::drawTongueAxis() { QFont font; font.setPointSize(6); d->painter.setFont(font); d->painter.setPen(qRgb(255, 255, 255)); biasedLine(0, 0, 0, d->pxrows - 1); biasedLine(0, d->pxrows-1, d->pxcols-1, d->pxrows - 1); for (int y = 1; y <= 9; y += 1) { QString s; int xstart = (y * (d->pxcols - 1)) / 10; int ystart = (y * (d->pxrows - 1)) / 10; s.sprintf("0.%d", y); biasedLine(xstart, d->pxrows - grids(1), xstart, d->pxrows - grids(4)); biasedText(xstart - grids(11), d->pxrows + grids(15), s); s.sprintf("0.%d", 10 - y); biasedLine(0, ystart, grids(3), ystart); biasedText(grids(-25), ystart + grids(5), s); } } void KisCIETongueWidget::drawTongueGrid() { d->painter.setPen(qRgb(128, 128, 128)); d->painter.setOpacity(0.5); for (int y = 1; y <= 9; y += 1) { int xstart = (y * (d->pxcols - 1)) / 10; int ystart = (y * (d->pxrows - 1)) / 10; biasedLine(xstart, grids(4), xstart, d->pxrows - grids(4) - 1); biasedLine(grids(7), ystart, d->pxcols-1-grids(7), ystart); } d->painter.setOpacity(1.0); } void KisCIETongueWidget::drawLabels() { QFont font; font.setPointSize(5); d->painter.setFont(font); for (int x = 450; x <= 650; x += (x > 470 && x < 600) ? 5 : 10) { QString wl; int bx = 0, by = 0, tx, ty; if (x < 520) { bx = grids(-22); by = grids(2); } else if (x < 535) { bx = grids(-8); by = grids(-6); } else { bx = grids(4); } int ix = (x - 380) / 5; QPointF * p = new QPointF(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]); int icx, icy; mapPoint(icx, icy, * p); tx = icx + ((x < 520) ? grids(-2) : ((x >= 535) ? grids(2) : 0)); ty = icy + ((x < 520) ? 0 : ((x >= 535) ? grids(-1) : grids(-2))); d->painter.setPen(qRgb(255, 255, 255)); biasedLine(icx, icy, tx, ty); QRgb Color = colorByCoord(icx, icy); d->painter.setPen(Color); wl.sprintf("%d", x); biasedText(icx+bx, icy+by, wl); } } void KisCIETongueWidget::drawSmallElipse(QPointF xy, int r, int g, int b, int sz) { int icx, icy; mapPoint(icx, icy, xy); d->painter.save(); d->painter.setRenderHint(QPainter::Antialiasing); d->painter.setPen(qRgb(r, g, b)); d->painter.drawEllipse(icx + d->xBias- sz/2, icy-sz/2, sz, sz); d->painter.setPen(qRgb(r/2, g/2, b/2)); int sz2 = sz-2; d->painter.drawEllipse(icx + d->xBias- sz2/2, icy-sz2/2, sz2, sz2); d->painter.restore(); } void KisCIETongueWidget::drawColorantTriangle() { d->painter.save(); d->painter.setPen(qRgb(80, 80, 80)); d->painter.setRenderHint(QPainter::Antialiasing); if (d->colorModel ==KisCIETongueWidget::RGBA) { drawSmallElipse((QPointF(d->Primaries[0],d->Primaries[1])), 255, 128, 128, 6); drawSmallElipse((QPointF(d->Primaries[3],d->Primaries[4])), 128, 255, 128, 6); drawSmallElipse((QPointF(d->Primaries[6],d->Primaries[7])), 128, 128, 255, 6); int x1, y1, x2, y2, x3, y3; mapPoint(x1, y1, (QPointF(d->Primaries[0],d->Primaries[1])) ); mapPoint(x2, y2, (QPointF(d->Primaries[3],d->Primaries[4])) ); mapPoint(x3, y3, (QPointF(d->Primaries[6],d->Primaries[7])) ); biasedLine(x1, y1, x2, y2); biasedLine(x2, y2, x3, y3); biasedLine(x3, y3, x1, y1); } /*else if (d->colorModel ==CMYK){ for (i=0; iPrimaries.size();i+++){ drawSmallElipse((QPointF(d->Primaries[0],d->Primaries[1])), 160, 160, 160, 6);//greyscale for now //int x1, y1, x2, y2; //mapPoint(x1, y1, (QPointF(d->Primaries[i],d->Primaries[i+1])) ); //mapPoint(x2, y2, (QPointF(d->Primaries[i+3],d->Primaries[i+4])) ); //biasedLine(x1, y1, x2, y2); } } */ d->painter.restore(); } void KisCIETongueWidget::drawWhitePoint() { drawSmallElipse(QPointF (d->whitePoint[0],d->whitePoint[1]), 255, 255, 255, 8); } void KisCIETongueWidget::drawGamut() { d->gamutMap=QPixmap(size()); d->gamutMap.fill(Qt::black); QPainter gamutPaint; gamutPaint.begin(&d->gamutMap); QPainterPath path; //gamutPaint.setCompositionMode(QPainter::CompositionMode_Clear); gamutPaint.setRenderHint(QPainter::Antialiasing); path.setFillRule(Qt::WindingFill); gamutPaint.setBrush(Qt::white); gamutPaint.setPen(Qt::white); int x, y = 0; if (!d->gamut.empty()) { gamutPaint.setOpacity(0.5); if (d->colorModel == KisCIETongueWidget::RGBA) { mapPoint(x, y, (QPointF(d->Primaries[0],d->Primaries[1])) ); path.moveTo(QPointF(x + d->xBias,y)); mapPoint(x, y, (QPointF(d->Primaries[3],d->Primaries[4])) ); path.lineTo(QPointF(x + d->xBias,y)); mapPoint(x, y, (QPointF(d->Primaries[6],d->Primaries[7])) ); path.lineTo(QPointF(x + d->xBias,y)); mapPoint(x, y, (QPointF(d->Primaries[0],d->Primaries[1])) ); path.lineTo(QPointF(x + d->xBias,y)); } gamutPaint.drawPath(path); gamutPaint.setOpacity(1.0); foreach (QPointF Point, d->gamut) { mapPoint(x, y, Point); gamutPaint.drawEllipse(x + d->xBias- 2, y-2, 4, 4); //Point.setX(x); //Point.setY(y); //path.lineTo(Point); } } gamutPaint.end(); d->painter.save(); d->painter.setOpacity(0.5); d->painter.setCompositionMode(QPainter::CompositionMode_Multiply); QRect area(d->xBias, 0, d->pxcols, d->pxrows); d->painter.drawPixmap(area,d->gamutMap, area); d->painter.setOpacity(1.0); d->painter.restore(); } void KisCIETongueWidget::updatePixmap() { d->needUpdatePixmap = false; d->pixmap = QPixmap(size()); if (d->cieTongueNeedsUpdate){ // Draw the CIE tongue curve. I don't see why we need to redraw it every time the whitepoint and such changes so we cache it. d->cieTongueNeedsUpdate = false; d->cietongue = QPixmap(size()); d->cietongue.fill(Qt::black); d->painter.begin(&d->cietongue); int pixcols = d->pixmap.width(); int pixrows = d->pixmap.height(); d->gridside = (qMin(pixcols, pixrows)) / 512.0; d->xBias = grids(32); d->yBias = grids(20); d->pxcols = pixcols - d->xBias; d->pxrows = pixrows - d->yBias; d->painter.setBackground(QBrush(qRgb(0, 0, 0))); d->painter.setPen(qRgb(255, 255, 255)); outlineTongue(); d->painter.end(); fillTongue(); d->painter.begin(&d->cietongue); drawTongueAxis(); drawLabels(); drawTongueGrid(); d->painter.end(); } d->pixmap = d->cietongue; d->painter.begin(&d->pixmap); //draw whitepoint and colorants if (d->whitePoint[2] > 0.0) { drawWhitePoint(); } if (d->Primaries[2] != 0.0) { drawColorantTriangle(); } drawGamut(); d->painter.end(); } void KisCIETongueWidget::paintEvent(QPaintEvent*) { QPainter p(this); // Widget is disable : drawing grayed frame. if ( !isEnabled() ) { p.fillRect(0, 0, width(), height(), palette().color(QPalette::Disabled, QPalette::Background)); QPen pen(palette().color(QPalette::Disabled, QPalette::Foreground)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); p.setPen(pen); p.drawRect(0, 0, width(), height()); return; } // No profile data to show, or RAW file if (!d->profileDataAvailable) { p.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Background)); QPen pen(palette().color(QPalette::Active, QPalette::Text)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); p.setPen(pen); p.drawRect(0, 0, width(), height()); if (d->uncalibratedColor) { p.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("Uncalibrated color space")); } else { p.setPen(Qt::red); p.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("No profile available...")); } return; } // Create CIE tongue if needed if (d->needUpdatePixmap) { updatePixmap(); } // draw prerendered tongue p.drawPixmap(0, 0, d->pixmap); } void KisCIETongueWidget::resizeEvent(QResizeEvent* event) { Q_UNUSED(event); setMinimumHeight(width()); setMaximumHeight(width()); d->needUpdatePixmap = true; d->cieTongueNeedsUpdate = true; } void KisCIETongueWidget::slotProgressTimerDone() { update(); d->progressTimer->start(200); } diff --git a/libs/ui/widgets/kis_cmb_composite.cc b/libs/ui/widgets/kis_cmb_composite.cc index a467a6e0b6..eae6887832 100644 --- a/libs/ui/widgets/kis_cmb_composite.cc +++ b/libs/ui/widgets/kis_cmb_composite.cc @@ -1,469 +1,481 @@ /* * kis_cmb_composite.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2011 Silvio Heinrich * * 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_cmb_composite.h" #include #include #include "kis_composite_ops_model.h" #include "kis_categorized_item_delegate.h" #include ////////////////////////////////////////////////////////////////////////////////////////// // ---- KisCompositeOpListWidget ------------------------------------------------------ // KisCompositeOpListWidget::KisCompositeOpListWidget(QWidget* parent): KisCategorizedListView(false, parent), m_model(new KisSortedCompositeOpListModel(this)) { setModel(m_model); setItemDelegate(new KisCategorizedItemDelegate(this)); } KisCompositeOpListWidget::~KisCompositeOpListWidget() { } KoID KisCompositeOpListWidget::selectedCompositeOp() const { KoID op; if (m_model->entryAt(op, currentIndex())) { return op; } return KoCompositeOpRegistry::instance().getDefaultCompositeOp(); } ////////////////////////////////////////////////////////////////////////////////////////// // ---- KisCompositeOpComboBox -------------------------------------------------------- // KisCompositeOpComboBox::KisCompositeOpComboBox(QWidget* parent): QComboBox(parent), m_model(new KisSortedCompositeOpListModel(this)), m_allowToHidePopup(true) { m_view = new KisCategorizedListView(true); setMaxVisibleItems(100); setSizeAdjustPolicy(AdjustToContents); m_view->setResizeMode(QListView::Adjust); setToolTip(i18n("Blending Mode")); setModel(m_model); setView(m_view); setItemDelegate(new KisCategorizedItemDelegate(this)); connect(m_view, SIGNAL(sigCategoryToggled(const QModelIndex&, bool)), SLOT(slotCategoryToggled(const QModelIndex&, bool))); connect(m_view, SIGNAL(sigEntryChecked(const QModelIndex&)), SLOT(slotEntryChecked(const QModelIndex&))); selectCompositeOp(KoCompositeOpRegistry::instance().getDefaultCompositeOp()); KisAction *action = 0; // // Cycle through blending modes // // Shift + + (plus) or – (minus) // KisAction *action = new KisAction(i18n("Next Blending Mode"), this); // action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Plus)); // connect(action, SIGNAL(triggered()), SLOT(slotNextBlendingMode())); // m_actions << action; // action = new KisAction(i18n("Previous Blending Mode"), this); // action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Minus)); // connect(action, SIGNAL(triggered()), SLOT(slotPreviousBlendingMode())); // m_actions << action; // Normal // Shift + Alt + N action = new KisAction(i18n("Select Normal Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_N)); connect(action, SIGNAL(triggered()), SLOT(slotNormal())); m_actions << action; // Dissolve // Shift + Alt + I action = new KisAction(i18n("Select Dissolve Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_I)); connect(action, SIGNAL(triggered()), SLOT(slotDissolve())); m_actions << action; // Behind (Brush tool only) // Shift + Alt + Q action = new KisAction(i18n("Select Behind Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Q)); connect(action, SIGNAL(triggered()), SLOT(slotBehind())); m_actions << action; // Clear (Brush tool only) // Shift + Alt + R action = new KisAction(i18n("Select Clear Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_R)); connect(action, SIGNAL(triggered()), SLOT(slotClear())); m_actions << action; // Darken // Shift + Alt + K action = new KisAction(i18n("Select Darken Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_K)); connect(action, SIGNAL(triggered()), SLOT(slotDarken())); m_actions << action; // Multiply // Shift + Alt + M action = new KisAction(i18n("Select Multiply Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_M)); connect(action, SIGNAL(triggered()), SLOT(slotMultiply())); m_actions << action; // Color Burn // Shift + Alt + B action = new KisAction(i18n("Select Color Burn Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_B)); connect(action, SIGNAL(triggered()), SLOT(slotColorBurn())); m_actions << action; // Linear Burn // Shift + Alt + A action = new KisAction(i18n("Select Linear Burn Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_A)); connect(action, SIGNAL(triggered()), SLOT(slotLinearBurn())); m_actions << action; // Lighten // Shift + Alt + G action = new KisAction(i18n("Select Lighten Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_G)); connect(action, SIGNAL(triggered()), SLOT(slotLighten())); m_actions << action; // Screen // Shift + Alt + S action = new KisAction(i18n("Select Screen Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_S)); connect(action, SIGNAL(triggered()), SLOT(slotScreen())); m_actions << action; // Color Dodge // Shift + Alt + D action = new KisAction(i18n("Select Color Dodge Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_D)); connect(action, SIGNAL(triggered()), SLOT(slotColorDodge())); m_actions << action; // Linear Dodge // Shift + Alt + W action = new KisAction(i18n("Select Linear Dodge Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_W)); connect(action, SIGNAL(triggered()), SLOT(slotLinearDodge())); m_actions << action; // Overlay // Shift + Alt + O action = new KisAction(i18n("Select Overlay Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_O)); connect(action, SIGNAL(triggered()), SLOT(slotOverlay())); m_actions << action; + // Hard Overlay + // Shift + Alt + P + action = new KisAction(i18n("Select Hard Overlay Blending Mode"), this); + action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_P)); + connect(action, SIGNAL(triggered()), SLOT(slotHardOverlay())); + m_actions << action; + // Soft Light // Shift + Alt + F action = new KisAction(i18n("Select Soft Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_F)); connect(action, SIGNAL(triggered()), SLOT(slotSoftLight())); m_actions << action; // Hard Light // Shift + Alt + H action = new KisAction(i18n("Select Hard Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_H)); connect(action, SIGNAL(triggered()), SLOT(slotHardLight())); m_actions << action; // Vivid Light // Shift + Alt + V action = new KisAction(i18n("Select Vivid Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_V)); connect(action, SIGNAL(triggered()), SLOT(slotVividLight())); m_actions << action; // Linear Light // Shift + Alt + J action = new KisAction(i18n("Select Linear Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_J)); connect(action, SIGNAL(triggered()), SLOT(slotLinearLight())); m_actions << action; // Pin Light // Shift + Alt + Z action = new KisAction(i18n("Select Pin Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Z)); connect(action, SIGNAL(triggered()), SLOT(slotPinLight())); m_actions << action; // Hard Mix // Shift + Alt + L action = new KisAction(i18n("Select Hard Mix Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_L)); connect(action, SIGNAL(triggered()), SLOT(slotHardMix())); m_actions << action; // Difference // Shift + Alt + E action = new KisAction(i18n("Select Difference Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_E)); connect(action, SIGNAL(triggered()), SLOT(slotDifference())); m_actions << action; // Exclusion // Shift + Alt + X action = new KisAction(i18n("Select Exclusion Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_X)); connect(action, SIGNAL(triggered()), SLOT(slotExclusion())); m_actions << action; // Hue // Shift + Alt + U action = new KisAction(i18n("Select Hue Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_U)); connect(action, SIGNAL(triggered()), SLOT(slotHue())); m_actions << action; // Saturation // Shift + Alt + T action = new KisAction(i18n("Select Saturation Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_T)); connect(action, SIGNAL(triggered()), SLOT(slotSaturation())); m_actions << action; // Color // Shift + Alt + C action = new KisAction(i18n("Select Color Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_C)); connect(action, SIGNAL(triggered()), SLOT(slotColor())); m_actions << action; // Luminosity // Shift + Alt + Y action = new KisAction(i18n("Select Luminosity Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Y)); connect(action, SIGNAL(triggered()), SLOT(slotLuminosity())); m_actions << action; } KisCompositeOpComboBox::~KisCompositeOpComboBox() { delete m_view; } void KisCompositeOpComboBox::validate(const KoColorSpace *cs) { m_model->validate(cs); } void KisCompositeOpComboBox::selectCompositeOp(const KoID &op) { QModelIndex index = m_model->indexOf(op); setCurrentIndex(index.row()); } KoID KisCompositeOpComboBox::selectedCompositeOp() const { KoID op; if (m_model->entryAt(op, m_model->index(currentIndex(), 0))) { return op; } return KoCompositeOpRegistry::instance().getDefaultCompositeOp(); } QList KisCompositeOpComboBox::blendmodeActions() const { return m_actions; } void KisCompositeOpComboBox::slotCategoryToggled(const QModelIndex& index, bool toggled) { Q_UNUSED(index); Q_UNUSED(toggled); //NOTE: this will (should) fit the size of the // popup widget to the view // don't know if this is expected behaviour // on all supported platforms. // Thre is nothing written about this in the docs. showPopup(); } void KisCompositeOpComboBox::slotEntryChecked(const QModelIndex& index) { Q_UNUSED(index); m_allowToHidePopup = false; } void KisCompositeOpComboBox::hidePopup() { if (m_allowToHidePopup) { QComboBox::hidePopup(); } else { QComboBox::showPopup(); } m_allowToHidePopup = true; } void KisCompositeOpComboBox::slotNextBlendingMode() { if (currentIndex() < count()) { setCurrentIndex(currentIndex() + 1); } } void KisCompositeOpComboBox::slotPreviousBlendingMode() { if (currentIndex() > 0) { setCurrentIndex(currentIndex() - 1); } } void KisCompositeOpComboBox::slotNormal() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_OVER)); } void KisCompositeOpComboBox::slotDissolve() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DISSOLVE)); } void KisCompositeOpComboBox::slotBehind() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_BEHIND)); } void KisCompositeOpComboBox::slotClear() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_CLEAR)); } void KisCompositeOpComboBox::slotDarken() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DARKEN)); } void KisCompositeOpComboBox::slotMultiply() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_MULT)); } void KisCompositeOpComboBox::slotColorBurn() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_BURN)); } void KisCompositeOpComboBox::slotLinearBurn() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_BURN)); } void KisCompositeOpComboBox::slotLighten() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LIGHTEN)); } void KisCompositeOpComboBox::slotScreen() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SCREEN)); } void KisCompositeOpComboBox::slotColorDodge() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DODGE)); } void KisCompositeOpComboBox::slotLinearDodge() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_DODGE)); } void KisCompositeOpComboBox::slotOverlay() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_OVERLAY)); } +void KisCompositeOpComboBox::slotHardOverlay() +{ + selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_OVERLAY)); +} + void KisCompositeOpComboBox::slotSoftLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP)); } void KisCompositeOpComboBox::slotHardLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_LIGHT)); } void KisCompositeOpComboBox::slotVividLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_VIVID_LIGHT)); } void KisCompositeOpComboBox::slotLinearLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_LIGHT)); } void KisCompositeOpComboBox::slotPinLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_PIN_LIGHT)); } void KisCompositeOpComboBox::slotHardMix() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_MIX)); } void KisCompositeOpComboBox::slotDifference() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DIFF)); } void KisCompositeOpComboBox::slotExclusion() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_EXCLUSION)); } void KisCompositeOpComboBox::slotHue() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HUE)); } void KisCompositeOpComboBox::slotSaturation() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SATURATION)); } void KisCompositeOpComboBox::slotColor() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_COLOR)); } void KisCompositeOpComboBox::slotLuminosity() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LUMINIZE)); } diff --git a/libs/ui/widgets/kis_cmb_composite.h b/libs/ui/widgets/kis_cmb_composite.h index 8273233e59..eee12be527 100644 --- a/libs/ui/widgets/kis_cmb_composite.h +++ b/libs/ui/widgets/kis_cmb_composite.h @@ -1,103 +1,104 @@ /* * widgets/kis_cmb_composite.h - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2011 Silvio Heinrich * * 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_COMPOSITEOP_WIDGETS_H_ #define KIS_COMPOSITEOP_WIDGETS_H_ #include #include #include "kis_categorized_list_view.h" class KoID; class KoColorSpace; class KisSortedCompositeOpListModel; class KisAction; class KRITAUI_EXPORT KisCompositeOpListWidget: public KisCategorizedListView { public: KisCompositeOpListWidget(QWidget* parent=0); ~KisCompositeOpListWidget() override; KoID selectedCompositeOp() const; private: KisSortedCompositeOpListModel *m_model; }; class KRITAUI_EXPORT KisCompositeOpComboBox: public QComboBox { Q_OBJECT public: KisCompositeOpComboBox(QWidget* parent=0); ~KisCompositeOpComboBox() override; void hidePopup() override; void validate(const KoColorSpace *cs); void selectCompositeOp(const KoID &op); KoID selectedCompositeOp() const; QList blendmodeActions() const; private Q_SLOTS: void slotCategoryToggled(const QModelIndex& index, bool toggled); void slotEntryChecked(const QModelIndex& index); void slotNextBlendingMode(); void slotPreviousBlendingMode(); void slotNormal(); void slotDissolve(); void slotBehind(); void slotClear(); void slotDarken(); void slotMultiply(); void slotColorBurn(); void slotLinearBurn(); void slotLighten(); void slotScreen(); void slotColorDodge(); void slotLinearDodge(); void slotOverlay(); + void slotHardOverlay(); void slotSoftLight(); void slotHardLight(); void slotVividLight(); void slotLinearLight(); void slotPinLight(); void slotHardMix(); void slotDifference(); void slotExclusion(); void slotHue(); void slotSaturation(); void slotColor(); void slotLuminosity(); private: KisSortedCompositeOpListModel *m_model; KisCategorizedListView *m_view; bool m_allowToHidePopup; QList m_actions; }; #endif // KIS_COMPOSITEOP_WIDGETS_H_ diff --git a/libs/ui/widgets/kis_color_space_selector.cc b/libs/ui/widgets/kis_color_space_selector.cc index 0896d42ebd..870c115705 100644 --- a/libs/ui/widgets/kis_color_space_selector.cc +++ b/libs/ui/widgets/kis_color_space_selector.cc @@ -1,275 +1,274 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * * 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_color_space_selector.h" #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselector.h" struct KisColorSpaceSelector::Private { Ui_WdgColorSpaceSelector* colorSpaceSelector; QString knsrcFile; bool profileValid; QString defaultsuffix; }; KisColorSpaceSelector::KisColorSpaceSelector(QWidget* parent) : QWidget(parent), m_advancedSelector(0), d(new Private) { setObjectName("KisColorSpaceSelector"); d->colorSpaceSelector = new Ui_WdgColorSpaceSelector; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbDepths(const KoID &))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbProfiles())); connect(d->colorSpaceSelector->cmbProfile, SIGNAL(activated(const QString &)), this, SLOT(colorSpaceChanged())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); d->defaultsuffix = " "+i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)"); connect(d->colorSpaceSelector->bnAdvanced, SIGNAL(clicked()), this, SLOT(slotOpenAdvancedSelector())); fillCmbProfiles(); } KisColorSpaceSelector::~KisColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisColorSpaceSelector::fillCmbProfiles() { - QString s = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); - d->colorSpaceSelector->cmbProfile->clear(); + const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); + const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); - const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s); - if (csf == 0) return; + d->colorSpaceSelector->cmbProfile->clear(); - QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf); + QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } qSort(profileNames); Q_FOREACH (QString stringName, profileNames) { - if (stringName==csf->defaultProfile()) { - d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName+d->defaultsuffix); + if (stringName == defaultProfileName) { + d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName + d->defaultsuffix); } else { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName); } } - d->colorSpaceSelector->cmbProfile->setCurrent(csf->defaultProfile()+d->defaultsuffix); + d->colorSpaceSelector->cmbProfile->setCurrent(defaultProfileName + d->defaultsuffix); colorSpaceChanged(); } void KisColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); // order the depth by name qSort(depths.begin(), depths.end(), sortBitDepthsComparer); d->colorSpaceSelector->cmbColorDepth->setIDList(depths); if (depths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } bool KisColorSpaceSelector::sortBitDepthsComparer(KoID depthOne, KoID depthTwo) { // to order these right, we need to first order by bit depth, then by if it is floating or not QString bitDepthOne = depthOne.name().split(" ")[0]; QString bitDepthTwo = depthTwo.name().split(" ")[0]; if (bitDepthOne.toInt() > bitDepthTwo.toInt()) { return false; } if (bitDepthOne.toInt() == bitDepthTwo.toInt()) { // bit depth number is the same, so now we need to compare if it is a floating type or not // the second value [1], just says 'bits', so that is why we look for [2] which has the float word QString bitDepthOneType = ""; QString bitDepthTwoType = ""; if (depthOne.name().split(" ").length() > 2) { bitDepthOneType = depthOne.name().split(" ")[2]; } if (depthTwo.name().split(" ").length() > 2) { bitDepthTwoType = depthTwo.name().split(" ")[2]; } if (bitDepthOneType.length() > bitDepthTwoType.length()) { return false; } } return true; } const KoColorSpace* KisColorSpaceSelector::currentColorSpace() { QString profilenamestring = d->colorSpaceSelector->cmbProfile->itemHighlighted(); if (profilenamestring.contains(d->defaultsuffix)) { profilenamestring.remove(d->defaultsuffix); return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } else { return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } } void KisColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillCmbDepths(id); } void KisColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillCmbProfiles(); } void KisColorSpaceSelector::setCurrentProfile(const QString& name) { d->colorSpaceSelector->cmbProfile->setCurrent(name); } void KisColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisColorSpaceSelector::showColorBrowserButton(bool showButton) { d->colorSpaceSelector->bnAdvanced->setVisible(showButton); } void KisColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->cmbProfile->count() != 0; d->profileValid = valid; emit(selectionChanged(valid)); if(valid) { emit colorSpaceChanged(currentColorSpace()); QString text = currentColorSpace()->profile()->name(); } } void KisColorSpaceSelector::installProfile() { QStringList mime; KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillCmbProfiles(); } void KisColorSpaceSelector::slotOpenAdvancedSelector() { if (!m_advancedSelector) { m_advancedSelector = new KisAdvancedColorSpaceSelector(this, "Select a Colorspace"); m_advancedSelector->setModal(true); if (currentColorSpace()) { m_advancedSelector->setCurrentColorSpace(currentColorSpace()); } connect(m_advancedSelector, SIGNAL(selectionChanged(bool)), this, SLOT(slotProfileValid(bool)) ); } QDialog::DialogCode result = (QDialog::DialogCode)m_advancedSelector->exec(); if (result) { if (d->profileValid==true) { setCurrentColorSpace(m_advancedSelector->currentColorSpace()); } } } void KisColorSpaceSelector::slotProfileValid(bool valid) { d->profileValid = valid; } diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp index f2d8794e18..f207c8c6a0 100644 --- a/libs/ui/widgets/kis_slider_spin_box.cpp +++ b/libs/ui/widgets/kis_slider_spin_box.cpp @@ -1,1037 +1,1038 @@ /* This file is part of the KDE project * Copyright (c) 2010 Justin Noel * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2015 Moritz Molch * * 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_slider_spin_box.h" #include #include #include #include #include #include #include #include #include #include #include - +#include "kis_cursor.h" #include "KisPart.h" #include "input/kis_input_manager.h" #include "kis_num_parser.h" class KisAbstractSliderSpinBoxPrivate { public: enum Style { STYLE_NOQUIRK, STYLE_PLASTIQUE, STYLE_BREEZE, STYLE_FUSION, }; QLineEdit* edit; QDoubleValidator* validator; bool upButtonDown; bool downButtonDown; int factor; int fastSliderStep; qreal slowFactor; qreal shiftPercent; bool shiftMode; QString prefix; QString suffix; qreal exponentRatio; int value; int maximum; int minimum; int singleStep; QSpinBox* dummySpinBox; Style style; bool blockUpdateSignalOnDrag; bool isDragging; bool parseInt; }; KisAbstractSliderSpinBox::KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate* _d) : QWidget(parent) , d_ptr(_d) { Q_D(KisAbstractSliderSpinBox); QEvent e(QEvent::StyleChange); changeEvent(&e); d->upButtonDown = false; d->downButtonDown = false; d->edit = new QLineEdit(this); d->edit->setFrame(false); d->edit->setAlignment(Qt::AlignCenter); d->edit->hide(); d->edit->setContentsMargins(0,0,0,0); d->edit->installEventFilter(this); //Make edit transparent d->edit->setAutoFillBackground(false); QPalette pal = d->edit->palette(); pal.setColor(QPalette::Base, Qt::transparent); d->edit->setPalette(pal); connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus())); d->validator = new QDoubleValidator(d->edit); d->value = 0; d->minimum = 0; d->maximum = 100; d->factor = 1.0; d->singleStep = 1; d->fastSliderStep = 5; d->slowFactor = 0.1; d->shiftMode = false; d->blockUpdateSignalOnDrag = false; d->isDragging = false; d->parseInt = false; setExponentRatio(1.0); + setCursor(KisCursor::splitHCursor()); //Set sane defaults setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); //dummy needed to fix a bug in the polyester theme d->dummySpinBox = new QSpinBox(this); d->dummySpinBox->hide(); } KisAbstractSliderSpinBox::~KisAbstractSliderSpinBox() { Q_D(KisAbstractSliderSpinBox); delete d; } void KisAbstractSliderSpinBox::showEdit() { Q_D(KisAbstractSliderSpinBox); if (d->edit->isVisible()) return; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE) { d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0,0,-2,0)); } else { d->edit->setGeometry(progressRect(spinBoxOptions())); } d->edit->setText(valueString()); d->edit->selectAll(); d->edit->show(); d->edit->setFocus(Qt::OtherFocusReason); update(); KisInputManager *inputManager = KisPart::instance()->currentInputManager(); if (inputManager) { inputManager->slotFocusOnEnter(false); } } void KisAbstractSliderSpinBox::hideEdit() { Q_D(KisAbstractSliderSpinBox); d->edit->hide(); update(); KisInputManager *inputManager = KisPart::instance()->currentInputManager(); if (inputManager) { inputManager->slotFocusOnEnter(true); } } void KisAbstractSliderSpinBox::paintEvent(QPaintEvent* e) { Q_D(KisAbstractSliderSpinBox); Q_UNUSED(e) QPainter painter(this); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: paintFusion(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: paintPlastique(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: paintBreeze(painter); break; default: paint(painter); break; } painter.end(); } void KisAbstractSliderSpinBox::paint(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); //Create options to draw spin box parts QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.rect.adjust(0, 2, 0, -2); //Draw "SpinBox".Clip off the area of the lineEdit to avoid double //borders being drawn painter.save(); painter.setClipping(true); QRect eraseRect(QPoint(rect().x(), rect().y()), QPoint(progressRect(spinOpts).right(), rect().bottom())); painter.setClipRegion(QRegion(rect()).subtracted(eraseRect)); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.setClipping(false); painter.restore(); QStyleOptionProgressBar progressOpts = progressBarOptions(); progressOpts.rect.adjust(0, 2, 0, -2); style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0); //Draw focus if necessary if (hasFocus() && d->edit->hasFocus()) { QStyleOptionFocusRect focusOpts; focusOpts.initFrom(this); focusOpts.rect = progressOpts.rect; focusOpts.backgroundColor = palette().color(QPalette::Window); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this); } } void KisAbstractSliderSpinBox::paintFusion(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.frame = true; spinOpts.rect.adjust(0, -1, 0, 1); //spinOpts.palette().setBrush(QPalette::Base, palette().highlight()); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(1,2,-4,-2); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setClipRect(leftRect.adjusted(0, -1, 1, 1)); painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); spinOpts.palette.setBrush(QPalette::Base, palette().highlight()); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); } painter.setClipping(false); } painter.restore(); } void KisAbstractSliderSpinBox::paintPlastique(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(2,0,-2,0); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); painter.drawRect(leftRect.adjusted(0,0,0,-1)); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect.adjusted(0,0,1,0)); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } } painter.restore(); } void KisAbstractSliderSpinBox::paintBreeze(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); QString valueText = progressOpts.text; progressOpts.text = ""; progressOpts.rect.adjust(0, 1, 0, -1); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this); style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this); painter.save(); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= progressOpts.rect.width()) { leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height()); } else if (progressIndicatorPos > progressOpts.rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = progressOpts.rect; rightRect = rightRect.subtracted(leftRect); painter.setClipRegion(rightRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } if (!leftRect.isNull()) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect); style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } } painter.restore(); } void KisAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Depress buttons or highlight slider //Also used to emulate mouse grab... if (e->buttons() & Qt::LeftButton) { if (upButtonRect(spinOpts).contains(e->pos())) { d->upButtonDown = true; } else if (downButtonRect(spinOpts).contains(e->pos())) { d->downButtonDown = true; } } else if (e->buttons() & Qt::RightButton) { showEdit(); } update(); } void KisAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); d->isDragging = false; //Step up/down for buttons //Emualting mouse grab too if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) { setInternalValue(d->value + d->singleStep); } else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) { setInternalValue(d->value - d->singleStep); } else if (progressRect(spinOpts).contains(e->pos()) && !(d->edit->isVisible()) && !(d->upButtonDown || d->downButtonDown)) { //Snap to percentage for progress area setInternalValue(valueForX(e->pos().x(),e->modifiers())); } else { // Confirm the last known value, since we might be ignoring move events setInternalValue(d->value); } d->upButtonDown = false; d->downButtonDown = false; update(); } void KisAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); if( e->modifiers() & Qt::ShiftModifier ) { if( !d->shiftMode ) { d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; } } else { d->shiftMode = false; } //Respect emulated mouse grab. if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown)) { d->isDragging = true; setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag); update(); } } void KisAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e) { Q_D(KisAbstractSliderSpinBox); switch (e->key()) { case Qt::Key_Up: case Qt::Key_Right: setInternalValue(d->value + d->singleStep); break; case Qt::Key_Down: case Qt::Key_Left: setInternalValue(d->value - d->singleStep); break; case Qt::Key_Shift: d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; break; case Qt::Key_Enter: //Line edit isn't "accepting" key strokes... case Qt::Key_Return: case Qt::Key_Escape: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_AltGr: case Qt::Key_Super_L: case Qt::Key_Super_R: break; default: showEdit(); d->edit->event(e); break; } } void KisAbstractSliderSpinBox::wheelEvent(QWheelEvent *e) { Q_D(KisAbstractSliderSpinBox); if ( e->delta() > 0) { setInternalValue(d->value + d->singleStep); } else { setInternalValue(d->value - d->singleStep); } update(); e->accept(); } void KisAbstractSliderSpinBox::commitEnteredValue() { Q_D(KisAbstractSliderSpinBox); //QLocale locale; bool ok = false; //qreal value = locale.toDouble(d->edit->text(), &ok) * d->factor; qreal value; if (d->parseInt) { value = KisNumericParser::parseIntegerMathExpr(d->edit->text(), &ok) * d->factor; } else { value = KisNumericParser::parseSimpleMathExpr(d->edit->text(), &ok) * d->factor; } if (ok) { setInternalValue(value); } } bool KisAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e) { Q_D(KisAbstractSliderSpinBox); if (recv == static_cast(d->edit) && e->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(e); switch (keyEvent->key()) { case Qt::Key_Enter: case Qt::Key_Return: { commitEnteredValue(); hideEdit(); return true; } case Qt::Key_Escape: d->edit->setText(valueString()); hideEdit(); return true; default: break; } } return false; } QSize KisAbstractSliderSpinBox::sizeHint() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QFont ft(font()); if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK) { // Some styles use bold font in progressbars // unfortunately there is no reliable way to check for that ft.setBold(true); } QFontMetrics fm(ft); QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size()); hint += QSize(0, 2); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: hint += QSize(8, 8); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: hint += QSize(8, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: hint += QSize(2, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK: // almost all "modern" styles have a margin around controls hint += QSize(6, 6); break; default: break; } //Getting the size of the buttons is a pain as the calcs require a rect //that is "big enough". We run the calc twice to get the "smallest" buttons //This code was inspired by QAbstractSpinBox QSize extra(1000, 0); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); hint += extra; spinOpts.rect.setSize(hint); return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint) .expandedTo(QApplication::globalStrut()); } QSize KisAbstractSliderSpinBox::minimumSizeHint() const { return sizeHint(); } QSize KisAbstractSliderSpinBox::minimumSize() const { return QWidget::minimumSize().expandedTo(minimumSizeHint()); } QStyleOptionSpinBox KisAbstractSliderSpinBox::spinBoxOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox opts; opts.initFrom(this); opts.frame = false; opts.buttonSymbols = QAbstractSpinBox::UpDownArrows; opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; //Disable non-logical buttons if (d->value == d->minimum) { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled; } else if (d->value == d->maximum) { opts.stepEnabled = QAbstractSpinBox::StepDownEnabled; } else { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled; } //Deal with depressed buttons if (d->upButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxUp; } else if (d->downButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxDown; } else { opts.activeSubControls = 0; } return opts; } QStyleOptionProgressBar KisAbstractSliderSpinBox::progressBarOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Create opts for drawing the progress portion QStyleOptionProgressBar progressOpts; progressOpts.initFrom(this); progressOpts.maximum = d->maximum; progressOpts.minimum = d->minimum; qreal minDbl = d->minimum; qreal dValues = (d->maximum - minDbl); progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl; progressOpts.text = d->prefix + valueString() + d->suffix; progressOpts.textAlignment = Qt::AlignCenter; progressOpts.textVisible = !(d->edit->isVisible()); //Change opts rect to be only the ComboBox's text area progressOpts.rect = progressRect(spinOpts); return progressOpts; } QRect KisAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const { const Q_D(KisAbstractSliderSpinBox); QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxEditField); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: ret.adjust(-2, 0, 1, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: ret.adjust(1, 0, 0, 0); break; default: break; } return ret; } QRect KisAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxUp); } QRect KisAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxDown); } int KisAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QRect correctedProgRect; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_FUSION) { correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0); } else if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE) { correctedProgRect = progressRect(spinOpts); } else { //Adjust for magic number in style code (margins) correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2); } //Compute the distance of the progress bar, in pixel qreal leftDbl = correctedProgRect.left(); qreal xDbl = x - leftDbl; //Compute the ration of the progress bar used, linearly (ignoring the exponent) qreal rightDbl = correctedProgRect.right(); qreal minDbl = d->minimum; qreal maxDbl = d->maximum; qreal dValues = (maxDbl - minDbl); qreal percent = (xDbl / (rightDbl - leftDbl)); //If SHIFT is pressed, movement should be slowed. if( modifiers & Qt::ShiftModifier ) { percent = d->shiftPercent + ( percent - d->shiftPercent ) * d->slowFactor; } //Final value qreal realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl); //If key CTRL is pressed, round to the closest step. if( modifiers & Qt::ControlModifier ) { qreal fstep = d->fastSliderStep; if( modifiers & Qt::ShiftModifier ) { fstep*=d->slowFactor; } realvalue = floor( (realvalue+fstep/2) / fstep ) * fstep; } //Return the value return int(realvalue); } void KisAbstractSliderSpinBox::setPrefix(const QString& prefix) { Q_D(KisAbstractSliderSpinBox); d->prefix = prefix; } void KisAbstractSliderSpinBox::setSuffix(const QString& suffix) { Q_D(KisAbstractSliderSpinBox); d->suffix = suffix; } void KisAbstractSliderSpinBox::setExponentRatio(qreal dbl) { Q_D(KisAbstractSliderSpinBox); Q_ASSERT(dbl > 0); d->exponentRatio = dbl; } void KisAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->blockUpdateSignalOnDrag = blockUpdateSignal; } void KisAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event) { event->accept(); } void KisAbstractSliderSpinBox::editLostFocus() { Q_D(KisAbstractSliderSpinBox); if (!d->edit->hasFocus()) { commitEnteredValue(); hideEdit(); } } void KisAbstractSliderSpinBox::setInternalValue(int value) { setInternalValue(value, false); } bool KisAbstractSliderSpinBox::isDragging() const { Q_D(const KisAbstractSliderSpinBox); return d->isDragging; } class KisSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisSliderSpinBox::KisSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisSliderSpinBoxPrivate) { Q_D(KisSliderSpinBox); d->parseInt = true; setRange(0,99); } KisSliderSpinBox::~KisSliderSpinBox() { } void KisSliderSpinBox::setRange(int minimum, int maximum) { Q_D(KisSliderSpinBox); d->minimum = minimum; d->maximum = maximum; d->fastSliderStep = (maximum-minimum+1)/20; d->validator->setRange(minimum, maximum, 0); update(); } int KisSliderSpinBox::minimum() const { const Q_D(KisSliderSpinBox); return d->minimum; } void KisSliderSpinBox::setMinimum(int minimum) { Q_D(KisSliderSpinBox); setRange(minimum, d->maximum); } int KisSliderSpinBox::maximum() const { const Q_D(KisSliderSpinBox); return d->maximum; } void KisSliderSpinBox::setMaximum(int maximum) { Q_D(KisSliderSpinBox); setRange(d->minimum, maximum); } int KisSliderSpinBox::fastSliderStep() const { const Q_D(KisSliderSpinBox); return d->fastSliderStep; } void KisSliderSpinBox::setFastSliderStep(int step) { Q_D(KisSliderSpinBox); d->fastSliderStep = step; } int KisSliderSpinBox::value() { Q_D(KisSliderSpinBox); return d->value; } void KisSliderSpinBox::setValue(int value) { setInternalValue(value, false); update(); } QString KisSliderSpinBox::valueString() const { const Q_D(KisSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value, 'f', d->validator->decimals()); } void KisSliderSpinBox::setSingleStep(int value) { Q_D(KisSliderSpinBox); d->singleStep = value; } void KisSliderSpinBox::setPageStep(int value) { Q_UNUSED(value); } void KisSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } class KisDoubleSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisDoubleSliderSpinBox::KisDoubleSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisDoubleSliderSpinBoxPrivate) { Q_D(KisDoubleSliderSpinBox); d->parseInt = false; } KisDoubleSliderSpinBox::~KisDoubleSliderSpinBox() { } void KisDoubleSliderSpinBox::setRange(qreal minimum, qreal maximum, int decimals) { Q_D(KisDoubleSliderSpinBox); d->factor = pow(10.0, decimals); d->minimum = minimum * d->factor; d->maximum = maximum * d->factor; //This code auto-compute a new step when pressing control. //A flag defaulting to "do not change the fast step" should be added, but it implies changing every call if(maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers d->fastSliderStep = int(pow(10.0, decimals)); } else if(decimals == 1) { d->fastSliderStep = (maximum-minimum)*d->factor/10; } else { d->fastSliderStep = (maximum-minimum)*d->factor/20; } d->validator->setRange(minimum, maximum, decimals); update(); setValue(value()); } qreal KisDoubleSliderSpinBox::minimum() const { const Q_D(KisAbstractSliderSpinBox); return d->minimum / d->factor; } void KisDoubleSliderSpinBox::setMinimum(qreal minimum) { Q_D(KisAbstractSliderSpinBox); setRange(minimum, d->maximum); } qreal KisDoubleSliderSpinBox::maximum() const { const Q_D(KisAbstractSliderSpinBox); return d->maximum / d->factor; } void KisDoubleSliderSpinBox::setMaximum(qreal maximum) { Q_D(KisAbstractSliderSpinBox); setRange(d->minimum, maximum); } qreal KisDoubleSliderSpinBox::fastSliderStep() const { const Q_D(KisAbstractSliderSpinBox); return d->fastSliderStep; } void KisDoubleSliderSpinBox::setFastSliderStep(qreal step) { Q_D(KisAbstractSliderSpinBox); d->fastSliderStep = step; } qreal KisDoubleSliderSpinBox::value() { Q_D(KisAbstractSliderSpinBox); return (qreal)d->value / d->factor; } void KisDoubleSliderSpinBox::setValue(qreal value) { Q_D(KisAbstractSliderSpinBox); setInternalValue(d->value = qRound(value * d->factor), false); update(); } void KisDoubleSliderSpinBox::setSingleStep(qreal value) { Q_D(KisAbstractSliderSpinBox); d->singleStep = value * d->factor; } QString KisDoubleSliderSpinBox::valueString() const { const Q_D(KisAbstractSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value / d->factor, 'f', d->validator->decimals()); } void KisDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } void KisAbstractSliderSpinBox::changeEvent(QEvent *e) { Q_D(KisAbstractSliderSpinBox); QWidget::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: if (style()->objectName() == "fusion") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_FUSION; } else if (style()->objectName() == "plastique") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE; } else if (style()->objectName() == "breeze") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE; } else { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK; } break; default: break; } } diff --git a/libs/widgets/KoResourceServerProvider.cpp b/libs/widgets/KoResourceServerProvider.cpp index f34cd582eb..4503c3a0b1 100644 --- a/libs/widgets/KoResourceServerProvider.cpp +++ b/libs/widgets/KoResourceServerProvider.cpp @@ -1,242 +1,260 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (C) 2011 Srikanth Tiyyagura This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KoResourceServerProvider.h" #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoResourcePaths.h" #include using namespace std; class GradientResourceServer : public KoResourceServer { public: GradientResourceServer(const QString& type, const QString& extensions) : KoResourceServer(type, extensions) , m_foregroundToTransparent(0) , m_foregroundToBackground(0) { insertSpecialGradients(); } void insertSpecialGradients() { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); QList stops; KoStopGradient* gradient = new KoStopGradient(); gradient->setType(QGradient::LinearGradient); gradient->setName("Foreground to Transparent"); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(QColor(0, 0, 0, 0), cs)); gradient->setStops(stops); gradient->setValid(true); gradient->setPermanent(true); addResource(gradient, false, true); m_foregroundToTransparent = gradient; gradient = new KoStopGradient(); gradient->setType(QGradient::LinearGradient); gradient->setName("Foreground to Background"); stops.clear(); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(Qt::white, cs)); gradient->setStops(stops); gradient->setValid(true); gradient->setPermanent(true); addResource(gradient, false, true); m_foregroundToBackground = gradient; } private: friend class KoResourceBundle; KoAbstractGradient* createResource( const QString & filename ) override { QString fileExtension; int index = filename.lastIndexOf('.'); if (index != -1) fileExtension = filename.mid(index).toLower(); KoAbstractGradient* grad = 0; if(fileExtension == ".svg" || fileExtension == ".kgr") grad = new KoStopGradient(filename); else if(fileExtension == ".ggr" ) grad = new KoSegmentGradient(filename); return grad; } QList< KoAbstractGradient* > sortedResources() override { QList< KoAbstractGradient* > resources = KoResourceServer::sortedResources(); QList< KoAbstractGradient* > sorted; if (m_foregroundToTransparent && resources.contains(m_foregroundToTransparent)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToTransparent))); } if (m_foregroundToBackground && resources.contains(m_foregroundToBackground)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToBackground))); } return sorted + resources; } KoAbstractGradient* m_foregroundToTransparent; KoAbstractGradient* m_foregroundToBackground; }; KoResourceLoaderThread::KoResourceLoaderThread(KoResourceServerBase * server) : QThread() , m_server(server) { m_fileNames = m_server->fileNames(); QStringList fileNames = m_server->blackListedFiles(); if (!fileNames.isEmpty()) { foreach (const QString &s, fileNames) { if (m_fileNames.contains(s)) { m_fileNames.removeAll(s); } } } connect(qApp, SIGNAL(aboutToQuit()), SLOT(barrier())); } KoResourceLoaderThread::~KoResourceLoaderThread() { } void KoResourceLoaderThread::loadSynchronously() { m_server->loadResources(m_fileNames); } void KoResourceLoaderThread::run() { m_server->loadResources(m_fileNames); } void KoResourceLoaderThread::barrier() { if(isRunning()) { wait(); } } struct Q_DECL_HIDDEN KoResourceServerProvider::Private { KoResourceServer* patternServer; KoResourceServer* gradientServer; KoResourceServer* paletteServer; + KoResourceServer *svgSymbolCollectionServer; KoResourceLoaderThread *paletteThread; KoResourceLoaderThread *gradientThread; KoResourceLoaderThread *patternThread; + KoResourceLoaderThread *svgSymbolCollectionThread; }; KoResourceServerProvider::KoResourceServerProvider() : d(new Private) { d->patternServer = new KoResourceServerSimpleConstruction("ko_patterns", "*.pat:*.jpg:*.gif:*.png:*.tif:*.xpm:*.bmp" ); if (!QFileInfo(d->patternServer->saveLocation()).exists()) { QDir().mkpath(d->patternServer->saveLocation()); } d->patternThread = new KoResourceLoaderThread(d->patternServer); d->patternThread->loadSynchronously(); // if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { // d->patternThread->barrier(); // } d->gradientServer = new GradientResourceServer("ko_gradients", "*.kgr:*.svg:*.ggr"); if (!QFileInfo(d->gradientServer->saveLocation()).exists()) { QDir().mkpath(d->gradientServer->saveLocation()); } d->gradientThread = new KoResourceLoaderThread(d->gradientServer); d->gradientThread->loadSynchronously(); // if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { // d->gradientThread->barrier(); // } d->paletteServer = new KoResourceServerSimpleConstruction("ko_palettes", "*.kpl:*.gpl:*.pal:*.act:*.aco:*.css:*.colors:*.xml:*.sbz"); if (!QFileInfo(d->paletteServer->saveLocation()).exists()) { QDir().mkpath(d->paletteServer->saveLocation()); } d->paletteThread = new KoResourceLoaderThread(d->paletteServer); d->paletteThread->loadSynchronously(); // if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { // d->paletteThread->barrier(); // } + + + d->svgSymbolCollectionServer = new KoResourceServerSimpleConstruction("symbols", "*.svg"); + if (!QFileInfo(d->svgSymbolCollectionServer->saveLocation()).exists()) { + QDir().mkpath(d->svgSymbolCollectionServer->saveLocation()); + } + d->svgSymbolCollectionThread = new KoResourceLoaderThread(d->svgSymbolCollectionServer); + d->svgSymbolCollectionThread ->loadSynchronously(); } KoResourceServerProvider::~KoResourceServerProvider() { delete d->patternThread; delete d->gradientThread; delete d->paletteThread; + delete d->svgSymbolCollectionThread; delete d->patternServer; delete d->gradientServer; delete d->paletteServer; + delete d->svgSymbolCollectionServer; delete d; } Q_GLOBAL_STATIC(KoResourceServerProvider, s_instance); KoResourceServerProvider* KoResourceServerProvider::instance() { return s_instance; } KoResourceServer* KoResourceServerProvider::patternServer(bool block) { if (block) d->patternThread->barrier(); return d->patternServer; } KoResourceServer* KoResourceServerProvider::gradientServer(bool block) { if (block) d->gradientThread->barrier(); return d->gradientServer; } KoResourceServer* KoResourceServerProvider::paletteServer(bool block) { if (block) d->paletteThread->barrier(); return d->paletteServer; } +KoResourceServer *KoResourceServerProvider::svgSymbolCollectionServer(bool block) +{ + if (block) d->svgSymbolCollectionThread->barrier(); + return d->svgSymbolCollectionServer; +} + diff --git a/libs/widgets/KoResourceServerProvider.h b/libs/widgets/KoResourceServerProvider.h index 54630b8b08..6b15cd973b 100644 --- a/libs/widgets/KoResourceServerProvider.h +++ b/libs/widgets/KoResourceServerProvider.h @@ -1,97 +1,100 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCESERVERPROVIDER_H #define KORESOURCESERVERPROVIDER_H #include #include #include #include "KoResourceServer.h" #include #include #include +#include +#include /** * KoResourceLoaderThread allows threaded loading of the resources of a resource server */ class KRITAWIDGETS_EXPORT KoResourceLoaderThread : public QThread { Q_OBJECT public: /** * Constructs a KoResourceLoaderThread for a server * @param server the server the resources will be loaded for */ explicit KoResourceLoaderThread(KoResourceServerBase *server); ~KoResourceLoaderThread() override; void loadSynchronously(); public Q_SLOTS: /** * Checks whether the thread has finished loading and waits * until it is finished if necessary */ void barrier(); protected: /** * Overridden from QThread */ void run() override; private: KoResourceServerBase * m_server; QStringList m_fileNames; }; /** * Provides default resource servers for gradients, patterns and palettes */ class KRITAWIDGETS_EXPORT KoResourceServerProvider : public QObject { Q_OBJECT public: KoResourceServerProvider(); ~KoResourceServerProvider() override; static KoResourceServerProvider* instance(); KoResourceServer* patternServer(bool block = true); KoResourceServer* gradientServer(bool block = true); KoResourceServer* paletteServer(bool block = true); + KoResourceServer* svgSymbolCollectionServer(bool block = true); private: KoResourceServerProvider(const KoResourceServerProvider&); KoResourceServerProvider operator=(const KoResourceServerProvider&); private: struct Private; Private* const d; }; #endif // KORESOURCESERVERPROVIDER_H diff --git a/libs/widgetutils/KoFileDialog.cpp b/libs/widgetutils/KoFileDialog.cpp index 4ce93277e1..6f89c3d36c 100644 --- a/libs/widgetutils/KoFileDialog.cpp +++ b/libs/widgetutils/KoFileDialog.cpp @@ -1,424 +1,415 @@ /* This file is part of the KDE project Copyright (C) 2013 - 2014 Yue Liu 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 "KoFileDialog.h" #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoFileDialog::Private { public: Private(QWidget *parent_, KoFileDialog::DialogType dialogType_, const QString caption_, const QString defaultDir_, const QString dialogName_) : parent(parent_) , type(dialogType_) , dialogName(dialogName_) , caption(caption_) , defaultDirectory(defaultDir_) , filterList(QStringList()) , defaultFilter(QString()) , swapExtensionOrder(false) { } ~Private() { } QWidget *parent; KoFileDialog::DialogType type; QString dialogName; QString caption; QString defaultDirectory; QString proposedFileName; QStringList filterList; QString defaultFilter; QScopedPointer fileDialog; QString mimeType; bool swapExtensionOrder; }; KoFileDialog::KoFileDialog(QWidget *parent, KoFileDialog::DialogType type, const QString &dialogName) : d(new Private(parent, type, "", getUsedDir(dialogName), dialogName)) { } KoFileDialog::~KoFileDialog() { delete d; } void KoFileDialog::setCaption(const QString &caption) { d->caption = caption; } void KoFileDialog::setDefaultDir(const QString &defaultDir) { - qDebug() << d->defaultDirectory << d->dialogName << getUsedDir(d->dialogName); if (d->defaultDirectory.isEmpty()) { QFileInfo f(defaultDir); d->defaultDirectory = f.absoluteFilePath(); } if (!QFileInfo(defaultDir).isDir()) { d->proposedFileName = QFileInfo(defaultDir).fileName(); } } void KoFileDialog::setImageFilters() { QStringList imageFilters; // add filters for all formats supported by QImage Q_FOREACH (const QByteArray &format, QImageReader::supportedImageFormats()) { imageFilters << QLatin1String("image/") + format; } setMimeTypeFilters(imageFilters); } void KoFileDialog::setMimeTypeFilters(const QStringList &filterList, QString defaultFilter) { d->filterList = getFilterStringListFromMime(filterList, true); if (!defaultFilter.isEmpty()) { QStringList defaultFilters = getFilterStringListFromMime(QStringList() << defaultFilter, false); if (defaultFilters.size() > 0) { defaultFilter = defaultFilters.first(); } } d->defaultFilter = defaultFilter; } QString KoFileDialog::selectedNameFilter() const { return d->fileDialog->selectedNameFilter(); } QString KoFileDialog::selectedMimeType() const { return d->mimeType; } void KoFileDialog::createFileDialog() { - qDebug() << "createFileDialog. Parent:" << d->parent - << "Caption:" << d->caption - << "Default directory:" << d->defaultDirectory - << "proposed filename" << d->proposedFileName - << "Default filter:" << d->defaultFilter; - d->fileDialog.reset(new QFileDialog(d->parent, d->caption, d->defaultDirectory + "/" + d->proposedFileName)); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif d->fileDialog->setOption(QFileDialog::DontUseNativeDialog, group.readEntry("DontUseNativeFileDialog", dontUseNative)); // qDebug() << "DontUseNativeDialog" << d->fileDialog->testOption(QFileDialog::DontUseNativeDialog) // << dontUseNative // << group.readEntry("DontUseNativeFileDialog", dontUseNative); d->fileDialog->setOption(QFileDialog::DontConfirmOverwrite, false); d->fileDialog->setOption(QFileDialog::HideNameFilterDetails, true); #ifdef Q_OS_OSX QList urls = d->fileDialog->sidebarUrls(); QUrl volumes = QUrl::fromLocalFile("\/Volumes"); if (!urls.contains(volumes)) { urls.append(volumes); } d->fileDialog->setSidebarUrls(urls); #endif if (d->type == SaveFile) { d->fileDialog->setAcceptMode(QFileDialog::AcceptSave); d->fileDialog->setFileMode(QFileDialog::AnyFile); } else { // open / import d->fileDialog->setAcceptMode(QFileDialog::AcceptOpen); if (d->type == ImportDirectory || d->type == OpenDirectory){ d->fileDialog->setFileMode(QFileDialog::Directory); d->fileDialog->setOption(QFileDialog::ShowDirsOnly, true); } else { // open / import file(s) if (d->type == OpenFile || d->type == ImportFile) { d->fileDialog->setFileMode(QFileDialog::ExistingFile); } else { // files d->fileDialog->setFileMode(QFileDialog::ExistingFiles); } } } d->fileDialog->setNameFilters(d->filterList); if (!d->proposedFileName.isEmpty()) { - qDebug() << "Finding the right mimetype for the given file" << d->defaultDirectory; QString mime = KisMimeDatabase::mimeTypeForFile(d->proposedFileName); QString description = KisMimeDatabase::descriptionForMimeType(mime); Q_FOREACH(const QString &filter, d->filterList) { - qDebug() << "\tConsidering" << filter; if (filter.startsWith(description)) { d->fileDialog->selectNameFilter(filter); break; } } } else if (!d->defaultFilter.isEmpty()) { d->fileDialog->selectNameFilter(d->defaultFilter); } if (d->type == ImportDirectory || d->type == ImportFile || d->type == ImportFiles || d->type == SaveFile) { d->fileDialog->setWindowModality(Qt::WindowModal); } } QString KoFileDialog::filename() { QString url; createFileDialog(); if (d->fileDialog->exec() == QDialog::Accepted) { url = d->fileDialog->selectedFiles().first(); } if (!url.isEmpty()) { if (d->type == SaveFile && QFileInfo(url).suffix().isEmpty()) { QString selectedFilter; // index 0 is all supported; if that is chosen, saveDocument will automatically make it .kra for (int i = 1; i < d->filterList.size(); ++i) { if (d->filterList[i].startsWith(d->fileDialog->selectedNameFilter())) { selectedFilter = d->filterList[i]; break; } } int start = selectedFilter.indexOf("*.") + 1; int end = selectedFilter.indexOf(" ", start); int n = end - start; QString extension = selectedFilter.mid(start, n); if (!(extension.contains(".") || url.endsWith("."))) { extension = "." + extension; } url = url + extension; } d->mimeType = KisMimeDatabase::mimeTypeForFile(url); saveUsedDir(url, d->dialogName); } return url; } QStringList KoFileDialog::filenames() { QStringList urls; createFileDialog(); if (d->fileDialog->exec() == QDialog::Accepted) { urls = d->fileDialog->selectedFiles(); } if (urls.size() > 0) { saveUsedDir(urls.first(), d->dialogName); } return urls; } QStringList KoFileDialog::splitNameFilter(const QString &nameFilter, QStringList *mimeList) { Q_ASSERT(mimeList); QStringList filters; QString description; if (nameFilter.contains("(")) { description = nameFilter.left(nameFilter.indexOf("(") -1).trimmed(); } QStringList entries = nameFilter.mid(nameFilter.indexOf("(") + 1).split(" ",QString::SkipEmptyParts ); entries.sort(); Q_FOREACH (QString entry, entries) { entry = entry.remove("*"); entry = entry.remove(")"); QString mimeType = KisMimeDatabase::mimeTypeForSuffix(entry); if (mimeType != "application/octet-stream") { if (!mimeList->contains(mimeType)) { mimeList->append(mimeType); filters.append(KisMimeDatabase::descriptionForMimeType(mimeType) + " ( *" + entry + " )"); } } else { filters.append(entry.remove(".").toUpper() + " " + description + " ( *." + entry + " )"); } } return filters; } const QStringList KoFileDialog::getFilterStringListFromMime(const QStringList &_mimeList, bool withAllSupportedEntry) { QStringList mimeSeen; // 1 QString allSupported; // 2 QString kritaNative; // 3 QString ora; QStringList ret; QStringList mimeList = _mimeList; mimeList.sort(); Q_FOREACH(const QString &mimeType, mimeList) { if (!mimeSeen.contains(mimeType)) { QString description = KisMimeDatabase::descriptionForMimeType(mimeType); if (description.isEmpty() && !mimeType.isEmpty()) { description = mimeType.split("/")[1]; if (description.startsWith("x-")) { description = description.remove(0, 2); } } QString oneFilter; QStringList patterns = KisMimeDatabase::suffixesForMimeType(mimeType); QStringList globPatterns; Q_FOREACH(const QString &pattern, patterns) { if (pattern.startsWith(".")) { globPatterns << "*" + pattern; } else if (pattern.startsWith("*.")) { globPatterns << pattern; } else { globPatterns << "*." + pattern; } } Q_FOREACH(const QString &glob, globPatterns) { if (d->swapExtensionOrder) { oneFilter.prepend(glob + " "); if (withAllSupportedEntry) { allSupported.prepend(glob + " "); } #ifdef Q_OS_LINUX if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") { oneFilter.prepend(glob.toUpper() + " "); if (withAllSupportedEntry) { allSupported.prepend(glob.toUpper() + " "); } } #endif } else { oneFilter.append(glob + " "); if (withAllSupportedEntry) { allSupported.append(glob + " "); } #ifdef Q_OS_LINUX if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") { oneFilter.append(glob.toUpper() + " "); if (withAllSupportedEntry) { allSupported.append(glob.toUpper() + " "); } } #endif } } Q_ASSERT(!description.isEmpty()); oneFilter = description + " ( " + oneFilter + ")"; if (mimeType == "application/x-krita") { kritaNative = oneFilter; continue; } if (mimeType == "image/openraster") { ora = oneFilter; continue; } else { ret << oneFilter; } mimeSeen << mimeType; } } ret.sort(); ret.removeDuplicates(); if (!ora.isEmpty()) ret.prepend(ora); if (!kritaNative.isEmpty()) ret.prepend(kritaNative); if (!allSupported.isEmpty()) ret.prepend(i18n("All supported formats") + " ( " + allSupported + (")")); return ret; } QString KoFileDialog::getUsedDir(const QString &dialogName) { if (dialogName.isEmpty()) return ""; KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString dir = group.readEntry(dialogName, ""); return dir; } void KoFileDialog::saveUsedDir(const QString &fileName, const QString &dialogName) { if (dialogName.isEmpty()) return; if (d->type == SaveFile) return; QFileInfo fileInfo(fileName); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry(dialogName, fileInfo.absolutePath()); } diff --git a/libs/widgetutils/xmlgui/kxmlguibuilder.cpp b/libs/widgetutils/xmlgui/kxmlguibuilder.cpp index f8f1d447b4..2326d806ec 100644 --- a/libs/widgetutils/xmlgui/kxmlguibuilder.cpp +++ b/libs/widgetutils/xmlgui/kxmlguibuilder.cpp @@ -1,429 +1,433 @@ /* This file is part of the KDE project Copyright (C) 2000 Simon Hausmann David Faure 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 "kxmlguibuilder.h" #include "kxmlguiclient.h" #include "ktoolbar.h" #include "kmainwindow.h" #include "kxmlguiwindow.h" #include "kmenumenuhandler_p.h" #include #include #include #include #include #include #include #include #include #include #include #if defined(KCONFIG_BEFORE_5_24) # define authorizeAction authorizeKAction #endif using namespace KDEPrivate; class KXMLGUIBuilderPrivate { public: KXMLGUIBuilderPrivate() : m_client(0L) {} ~KXMLGUIBuilderPrivate() { } QWidget *m_widget; QString tagMainWindow; QString tagMenuBar; QString tagMenu; QString tagToolBar; QString tagStatusBar; QString tagSeparator; QString tagTearOffHandle; QString tagMenuTitle; QString attrName; QString attrLineSeparator; QString attrDomain; QString attrText1; QString attrText2; QString attrContext; QString attrIcon; KXMLGUIClient *m_client; KMenuMenuHandler *m_menumenuhandler; }; KXMLGUIBuilder::KXMLGUIBuilder(QWidget *widget) : d(new KXMLGUIBuilderPrivate) { d->m_widget = widget; d->tagMainWindow = QStringLiteral("mainwindow"); d->tagMenuBar = QStringLiteral("menubar"); d->tagMenu = QStringLiteral("menu"); d->tagToolBar = QStringLiteral("toolbar"); d->tagStatusBar = QStringLiteral("statusbar"); d->tagSeparator = QStringLiteral("separator"); d->tagTearOffHandle = QStringLiteral("tearoffhandle"); d->tagMenuTitle = QStringLiteral("title"); d->attrName = QStringLiteral("name"); d->attrLineSeparator = QStringLiteral("lineseparator"); d->attrDomain = QStringLiteral("translationDomain"); d->attrText1 = QStringLiteral("text"); d->attrText2 = QStringLiteral("Text"); d->attrContext = QStringLiteral("context"); d->attrIcon = QStringLiteral("icon"); d->m_menumenuhandler = new KMenuMenuHandler(this); } KXMLGUIBuilder::~KXMLGUIBuilder() { delete d->m_menumenuhandler; delete d; } QWidget *KXMLGUIBuilder::widget() { return d->m_widget; } QStringList KXMLGUIBuilder::containerTags() const { QStringList res; res << d->tagMenu << d->tagToolBar << d->tagMainWindow << d->tagMenuBar << d->tagStatusBar; return res; } QWidget *KXMLGUIBuilder::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction) { containerAction = 0; if (element.attribute(QStringLiteral("deleted")).toLower() == QLatin1String("true")) { return 0; } const QString tagName = element.tagName().toLower(); if (tagName == d->tagMainWindow) { KMainWindow *mainwindow = qobject_cast(d->m_widget); // could be 0 return mainwindow; } if (tagName == d->tagMenuBar) { KMainWindow *mainWin = qobject_cast(d->m_widget); QMenuBar *bar = 0; if (mainWin) { bar = mainWin->menuBar(); } if (!bar) { bar = new QMenuBar(d->m_widget); } bar->show(); return bar; } if (tagName == d->tagMenu) { // Look up to see if we are inside a mainwindow. If yes, then // use it as parent widget (to get kaction to plug itself into the // mainwindow). Don't use a popupmenu as parent widget, otherwise // the popup won't be hidden if it is used as a standalone menu as well. - // And we don't want to set the parent for a standalone popupmenu, - // otherwise its shortcuts appear. // // Note: menus with a parent of 0, coming from child clients, can be // leaked if the child client is deleted without a proper removeClient call, though. + QWidget *p = parent; + + if (!p && qobject_cast(d->m_widget)) { + p = d->m_widget; + } + while (p && !qobject_cast(p)) { p = p->parentWidget(); } QString name = element.attribute(d->attrName); if (!KAuthorized::authorizeAction(name)) { return 0; } QMenu *popup = new QMenu(p); popup->setObjectName(name); d->m_menumenuhandler->insertMenu(popup); QString i18nText; QDomElement textElem = element.namedItem(d->attrText1).toElement(); if (textElem.isNull()) { // try with capital T textElem = element.namedItem(d->attrText2).toElement(); } const QString text = textElem.text(); const QString context = textElem.attribute(d->attrContext); //qDebug(260) << "DOMAIN" << KLocalizedString::applicationDomain(); //qDebug(260) << "ELEMENT TEXT:" << text; if (text.isEmpty()) { // still no luck i18nText = i18n("No text"); } else { QByteArray domain = textElem.attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } if (context.isEmpty()) { i18nText = i18nd(domain.constData(), text.toUtf8().constData()); } else { i18nText = i18ndc(domain.constData(), context.toUtf8().constData(), text.toUtf8().constData()); } } //qDebug(260) << "ELEMENT i18n TEXT:" << i18nText; const QString icon = element.attribute(d->attrIcon); QIcon pix; if (!icon.isEmpty()) { pix = KisIconUtils::loadIcon(icon); } if (parent) { QAction *act = popup->menuAction(); if (!icon.isEmpty()) { act->setIcon(pix); } act->setText(i18nText); if (index == -1 || index >= parent->actions().count()) { parent->addAction(act); } else { parent->insertAction(parent->actions().value(index), act); } containerAction = act; containerAction->setObjectName(name); } return popup; } if (tagName == d->tagToolBar) { QString name = element.attribute(d->attrName); KToolBar *bar = static_cast(d->m_widget->findChild(name)); if (!bar) { bar = new KToolBar(name, d->m_widget, false); } if (qobject_cast(d->m_widget)) { if (d->m_client && !d->m_client->xmlFile().isEmpty()) { bar->addXMLGUIClient(d->m_client); } } bar->loadState(element); return bar; } if (tagName == d->tagStatusBar) { KMainWindow *mainWin = qobject_cast(d->m_widget); if (mainWin) { mainWin->statusBar()->show(); return mainWin->statusBar(); } QStatusBar *bar = new QStatusBar(d->m_widget); return bar; } return 0L; } void KXMLGUIBuilder::removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction) { // Warning parent can be 0L if (qobject_cast(container)) { if (parent) { parent->removeAction(containerAction); } delete container; } else if (qobject_cast(container)) { KToolBar *tb = static_cast(container); tb->saveState(element); delete tb; } else if (qobject_cast(container)) { QMenuBar *mb = static_cast(container); mb->hide(); // Don't delete menubar - it can be reused by createContainer. // If you decide that you do need to delete the menubar, make // sure that QMainWindow::d->mb does not point to a deleted // menubar object. } else if (qobject_cast(container)) { if (qobject_cast(d->m_widget)) { container->hide(); } else { delete static_cast(container); } } else { qWarning() << "Unhandled container to remove : " << container->metaObject()->className(); } } QStringList KXMLGUIBuilder::customTags() const { QStringList res; res << d->tagSeparator << d->tagTearOffHandle << d->tagMenuTitle; return res; } QAction *KXMLGUIBuilder::createCustomElement(QWidget *parent, int index, const QDomElement &element) { QAction *before = 0L; if (index > 0 && index < parent->actions().count()) { before = parent->actions().at(index); } const QString tagName = element.tagName().toLower(); if (tagName == d->tagSeparator) { if (QMenu *menu = qobject_cast(parent)) { // QMenu already cares for leading/trailing/repeated separators // no need to check anything return menu->insertSeparator(before); } else if (QMenuBar *bar = qobject_cast(parent)) { QAction *separatorAction = new QAction(bar); separatorAction->setSeparator(true); bar->insertAction(before, separatorAction); return separatorAction; } else if (KToolBar *bar = qobject_cast(parent)) { /* FIXME KAction port - any need to provide a replacement for lineSeparator/normal separator? bool isLineSep = true; QDomNamedNodeMap attributes = element.attributes(); unsigned int i = 0; for (; i < attributes.length(); i++ ) { QDomAttr attr = attributes.item( i ).toAttr(); if ( attr.name().toLower() == d->attrLineSeparator && attr.value().toLower() == QStringLiteral("false") ) { isLineSep = false; break; } } if ( isLineSep ) return bar->insertSeparator( index ? bar->actions()[index - 1] : 0L ); else*/ return bar->insertSeparator(before); } } else if (tagName == d->tagTearOffHandle) { static_cast(parent)->setTearOffEnabled(true); } else if (tagName == d->tagMenuTitle) { if (QMenu *m = qobject_cast(parent)) { QString i18nText; const QString text = element.text(); if (text.isEmpty()) { i18nText = i18n("No text"); } else { QByteArray domain = element.attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } i18nText = i18nd(domain.constData(), qPrintable(text)); } QString icon = element.attribute(d->attrIcon); QIcon pix; if (!icon.isEmpty()) { pix = KisIconUtils::loadIcon(icon); } if (!icon.isEmpty()) { return m->insertSection(before, pix, i18nText); } else { return m->insertSection(before, i18nText); } } } QAction *blank = new QAction(parent); blank->setVisible(false); parent->insertAction(before, blank); return blank; } void KXMLGUIBuilder::removeCustomElement(QWidget *parent, QAction *action) { parent->removeAction(action); } KXMLGUIClient *KXMLGUIBuilder::builderClient() const { return d->m_client; } void KXMLGUIBuilder::setBuilderClient(KXMLGUIClient *client) { d->m_client = client; } void KXMLGUIBuilder::finalizeGUI(KXMLGUIClient *) { KXmlGuiWindow *window = qobject_cast(d->m_widget); if (!window) { return; } #if 0 KToolBar *toolbar = 0; QListIterator it(((KMainWindow *)d->m_widget)->toolBarIterator()); while ((toolbar = it.current())) { //qDebug(260) << "KXMLGUIBuilder::finalizeGUI toolbar=" << (void*)toolbar; ++it; toolbar->positionYourself(); } #else window->finalizeGUI(false); #endif } void KXMLGUIBuilder::virtual_hook(int, void *) { /*BASE::virtual_hook( id, data );*/ } diff --git a/packaging/linux/snap/setup/gui/krita.desktop b/packaging/linux/snap/setup/gui/krita.desktop index 86bb57a2e1..25b662520e 100755 --- a/packaging/linux/snap/setup/gui/krita.desktop +++ b/packaging/linux/snap/setup/gui/krita.desktop @@ -1,125 +1,126 @@ [Desktop Entry] Name=Krita Name[af]=Krita Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita Name[zh_TW]=繪圖_Krita Exec=krita %U GenericName=Digital Painting GenericName[bs]=Digitalno Bojenje GenericName[ca]=Dibuix digital GenericName[ca@valencia]=Dibuix digital GenericName[da]=Digital tegning GenericName[de]=Digitales Malen GenericName[el]=Ψηφιακή ζωγραφική GenericName[en_GB]=Digital Painting GenericName[es]=Pintura digital GenericName[et]=Digitaalne joonistamine GenericName[eu]=Pintura digitala GenericName[fi]=Digitaalimaalaus GenericName[fr]=Peinture numérique GenericName[gl]=Debuxo dixital GenericName[hu]=Digitális festészet GenericName[ia]=Pintura Digital GenericName[it]=Pittura digitale GenericName[ja]=デジタルペインティング GenericName[kk]=Цифрлық сурет салу GenericName[lt]=Skaitmeninis piešimas GenericName[mr]=डिजिटल पेंटिंग GenericName[nb]=Digital maling GenericName[nl]=Digitaal schilderen GenericName[pl]=Cyfrowe malowanie GenericName[pt]=Pintura Digital GenericName[pt_BR]=Pintura digital GenericName[ru]=Цифровая живопись GenericName[sk]=Digitálne maľovanie GenericName[sl]=Digitalno slikanje GenericName[sv]=Digital målning GenericName[tr]=Sayısal Boyama GenericName[ug]=سىفىرلىق رەسىم سىزغۇ GenericName[uk]=Цифрове малювання GenericName[x-test]=xxDigital Paintingxx GenericName[zh_CN]=数字绘画 MimeType=application/x-krita;image/openraster;application/x-krita-paintoppreset; Comment=Pixel-based image manipulation program for the Calligra Suite Comment[ca]=Programa de manipulació d'imatges basades en píxels per a la Suite Calligra Comment[ca@valencia]=Programa de manipulació d'imatges basades en píxels per a la Suite Calligra Comment[de]=Pixelbasiertes Bildbearbeitungsprogramm für die Calligra-Suite Comment[en_GB]=Pixel-based image manipulation program for the Calligra Suite Comment[es]=Programa de manipulación de imágenes basado en píxeles para la suite Calligra Comment[et]=Calligra pikslipõhine pilditöötluse rakendus Comment[it]=Programma di manipolazione delle immagini basato su pixel per Calligra Suite Comment[nl]=Afbeeldingsbewerkingsprogramma gebaseerd op pixels voor de Calligra Suite Comment[pl]=Program do obróbki obrazów na poziomie pikseli dla Pakietu Calligra Comment[pt]='Plugin' de manipulação de imagens em pixels para o Calligra Stage Comment[pt_BR]=Programa de manipulação de imagens baseado em pixels para o Calligra Suite Comment[ru]=Программа редактирования пиксельной анимации для the Calligra Suite Comment[sk]=Program na manipuláciu s pixelmi pre Calligra Suite Comment[sv]=Bildpunktsbaserat bildbehandlingsprogram för Calligra-sviten +Comment[tr]=Calligra Suite için Pixel tabanlı görüntü düzenleme programı Comment[uk]=Програма для роботи із растровими зображеннями для комплексу програм Calligra Comment[x-test]=xxPixel-based image manipulation program for the Calligra Suitexx Comment[zh_CN]=Calligra 套件的位图图像处理程序 Type=Application Icon=${SNAP}/meta/gui/calligrakrita.png Categories=Qt;KDE;Graphics; X-KDE-NativeMimeType=application/x-krita X-KDE-ExtraNativeMimeTypes= StartupNotify=true X-Krita-Version=28 diff --git a/plugins/color/lcms2engine/tests/TestKoColorSpaceRegistry.cpp b/plugins/color/lcms2engine/tests/TestKoColorSpaceRegistry.cpp index ac3ee61a46..5837e16151 100644 --- a/plugins/color/lcms2engine/tests/TestKoColorSpaceRegistry.cpp +++ b/plugins/color/lcms2engine/tests/TestKoColorSpaceRegistry.cpp @@ -1,95 +1,89 @@ #include "TestKoColorSpaceRegistry.h" #include #include "KoColorSpaceRegistry.h" #include "KoColorSpace.h" #include "RgbU8ColorSpace.h" #include "RgbU16ColorSpace.h" #include "LabColorSpace.h" void TestKoColorSpaceRegistry::testConstruction() { KoColorSpaceRegistry *instance = KoColorSpaceRegistry::instance(); Q_ASSERT(instance); } void TestKoColorSpaceRegistry::testRgbU8() { - QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(RGBAColorModelID, - Integer8BitsColorDepthID); - const KoColorSpaceFactory *colorSpaceFactory = KoColorSpaceRegistry::instance()->colorSpaceFactory(colorSpaceId); - QVERIFY(colorSpaceFactory != 0); + const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(RGBAColorModelID, + Integer8BitsColorDepthID); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); QVERIFY(colorSpace != 0); const KoColorProfile *profile = colorSpace->profile(); QVERIFY(profile != 0); - QCOMPARE(profile->name(), colorSpaceFactory->defaultProfile()); + QCOMPARE(profile->name(), KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId)); cmsHPROFILE lcmsProfile = cmsCreate_sRGBProfile(); QString testProfileName = "TestRGBU8ProfileName"; cmsWriteTag(lcmsProfile, cmsSigProfileDescriptionTag, testProfileName.toLatin1().constData()); cmsWriteTag(lcmsProfile, cmsSigDeviceModelDescTag, testProfileName.toLatin1().constData()); cmsWriteTag(lcmsProfile, cmsSigDeviceMfgDescTag, ""); } void TestKoColorSpaceRegistry::testRgbU16() { - QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(RGBAColorModelID, - Integer16BitsColorDepthID); - const KoColorSpaceFactory *colorSpaceFactory = KoColorSpaceRegistry::instance()->colorSpaceFactory(colorSpaceId); - QVERIFY(colorSpaceFactory != 0); + const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(RGBAColorModelID, + Integer16BitsColorDepthID); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb16(); QVERIFY(colorSpace != 0); const KoColorProfile *profile = colorSpace->profile(); QVERIFY(profile != 0); - QCOMPARE(profile->name(), colorSpaceFactory->defaultProfile()); + QCOMPARE(profile->name(), KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId)); cmsHPROFILE lcmsProfile = cmsCreate_sRGBProfile(); QString testProfileName = "TestRGBU16ProfileName"; cmsWriteTag(lcmsProfile, cmsSigProfileDescriptionTag, testProfileName.toLatin1().constData()); cmsWriteTag(lcmsProfile, cmsSigDeviceModelDescTag, testProfileName.toLatin1().constData()); cmsWriteTag(lcmsProfile, cmsSigDeviceMfgDescTag, ""); } void TestKoColorSpaceRegistry::testLab() { - QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(LABAColorModelID, - Integer16BitsColorDepthID); - const KoColorSpaceFactory *colorSpaceFactory = KoColorSpaceRegistry::instance()->colorSpaceFactory(colorSpaceId); - QVERIFY(colorSpaceFactory != 0); + const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(LABAColorModelID, + Integer16BitsColorDepthID); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->lab16(); QVERIFY(colorSpace != 0); const KoColorProfile *profile = colorSpace->profile(); QVERIFY(profile != 0); - QCOMPARE(profile->name(), colorSpaceFactory->defaultProfile()); + QCOMPARE(profile->name(), KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId)); cmsCIExyY whitepoint; whitepoint.x = 0.33; whitepoint.y = 0.33; whitepoint.Y = 1.0; cmsHPROFILE lcmsProfile = cmsCreateLab2Profile(&whitepoint); QString testProfileName = "TestLabProfileName"; cmsWriteTag(lcmsProfile, cmsSigProfileDescriptionTag, testProfileName.toLatin1().constData()); cmsWriteTag(lcmsProfile, cmsSigDeviceModelDescTag, testProfileName.toLatin1().constData()); cmsWriteTag(lcmsProfile, cmsSigDeviceMfgDescTag, ""); } QTEST_GUILESS_MAIN(TestKoColorSpaceRegistry) diff --git a/plugins/dockers/animation/kis_animation_curves_model.cpp b/plugins/dockers/animation/kis_animation_curves_model.cpp index a61751a9dc..ea1d826dc0 100644 --- a/plugins/dockers/animation/kis_animation_curves_model.cpp +++ b/plugins/dockers/animation/kis_animation_curves_model.cpp @@ -1,412 +1,413 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_curves_model.h" #include #include "kis_global.h" #include "kis_image.h" #include "kis_node.h" #include "kis_keyframe_channel.h" #include "kis_scalar_keyframe_channel.h" #include "kis_post_execution_undo_adapter.h" #include "kis_animation_utils.h" #include "kis_processing_applicator.h" #include "kis_command_utils.h" #include "KisImageBarrierLockerWithFeedback.h" struct KisAnimationCurve::Private { Private(KisScalarKeyframeChannel *channel, QColor color) : channel(channel) , color(color) , visible(true) {} KisScalarKeyframeChannel *channel; QColor color; bool visible; }; KisAnimationCurve::KisAnimationCurve(KisScalarKeyframeChannel *channel, QColor color) : m_d(new Private(channel, color)) {} KisScalarKeyframeChannel *KisAnimationCurve::channel() const { return m_d->channel; } QColor KisAnimationCurve::color() const { return m_d->color; } void KisAnimationCurve::setVisible(bool visible) { m_d->visible = visible; } bool KisAnimationCurve::visible() const { return m_d->visible; } struct KisAnimationCurvesModel::Private { QList curves; int nextColorHue; KUndo2Command *undoCommand; Private() : nextColorHue(0) , undoCommand(0) {} KisAnimationCurve *getCurveAt(const QModelIndex& index) { if (!index.isValid()) return 0; int row = index.row(); if (row < 0 || row >= curves.size()) { return 0; } return curves.at(row); } int rowForCurve(KisAnimationCurve *curve) { return curves.indexOf(curve); } int rowForChannel(KisKeyframeChannel *channel) { for (int row = 0; row < curves.count(); row++) { if (curves.at(row)->channel() == channel) return row; } return -1; } QColor chooseNextColor() { if (curves.isEmpty()) nextColorHue = 0; QColor color = QColor::fromHsv(nextColorHue, 255, 255); nextColorHue += 94; // Value chosen experimentally for providing distinct colors nextColorHue = nextColorHue & 0xff; return color; } }; KisAnimationCurvesModel::KisAnimationCurvesModel(QObject *parent) : KisTimeBasedItemModel(parent) , m_d(new Private()) {} KisAnimationCurvesModel::~KisAnimationCurvesModel() { qDeleteAll(m_d->curves); } int KisAnimationCurvesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_d->curves.size(); } QVariant KisAnimationCurvesModel::data(const QModelIndex &index, int role) const { KisAnimationCurve *curve = m_d->getCurveAt(index); if (curve) { KisScalarKeyframeChannel *channel = curve->channel(); int time = index.column(); KisKeyframeSP keyframe = channel->keyframeAt(time); switch (role) { case SpecialKeyframeExists: return !keyframe.isNull(); case ScalarValueRole: return channel->interpolatedValue(time); case LeftTangentRole: return (keyframe.isNull()) ? QVariant() : keyframe->leftTangent(); case RightTangentRole: return (keyframe.isNull()) ? QVariant() : keyframe->rightTangent(); case InterpolationModeRole: return (keyframe.isNull()) ? QVariant() : keyframe->interpolationMode(); case TangentsModeRole: return (keyframe.isNull()) ? QVariant() : keyframe->tangentsMode(); case CurveColorRole: return curve->color(); case CurveVisibleRole: return curve->visible(); case PreviousKeyframeTime: { KisKeyframeSP active = channel->activeKeyframeAt(time); if (active.isNull()) return QVariant(); if (active->time() < time) { return active->time(); } KisKeyframeSP previous = channel->previousKeyframe(active); if (previous.isNull()) return QVariant(); return previous->time(); } case NextKeyframeTime: { KisKeyframeSP active = channel->activeKeyframeAt(time); if (active.isNull()) { KisKeyframeSP first = channel->firstKeyframe(); if (!first.isNull() && first->time() > time) { return first->time(); } return QVariant(); } KisKeyframeSP next = channel->nextKeyframe(active); if (next.isNull()) return QVariant(); return next->time(); } default: break; } } return KisTimeBasedItemModel::data(index, role); } bool KisAnimationCurvesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; KisScalarKeyframeChannel *channel = m_d->getCurveAt(index)->channel(); KUndo2Command *command = m_d->undoCommand; switch (role) { case ScalarValueRole: { KisKeyframeSP keyframe = channel->keyframeAt(index.column()); if (keyframe) { if (!command) command = new KUndo2Command(kundo2_i18n("Adjust keyframe")); channel->setScalarValue(keyframe, value.toReal(), command); } else { if (!command) command = new KUndo2Command(kundo2_i18n("Insert keyframe")); auto *addKeyframeCommand = new KisScalarKeyframeChannel::AddKeyframeCommand( channel, index.column(), value.toReal(), command); addKeyframeCommand->redo(); } } break; case LeftTangentRole: case RightTangentRole: { KisKeyframeSP keyframe = channel->keyframeAt(index.column()); if (!keyframe) return false; QPointF leftTangent = (role == LeftTangentRole ? value.toPointF() : keyframe->leftTangent()); QPointF rightTangent = (role == RightTangentRole ? value.toPointF() : keyframe->rightTangent()); if (!command) command = new KUndo2Command(kundo2_i18n("Adjust tangent")); channel->setInterpolationTangents(keyframe, keyframe->tangentsMode(), leftTangent, rightTangent, command); } break; case InterpolationModeRole: { KisKeyframeSP keyframe = channel->keyframeAt(index.column()); if (!keyframe) return false; if (!command) command = new KUndo2Command(kundo2_i18n("Set interpolation mode")); channel->setInterpolationMode(keyframe, (KisKeyframe::InterpolationMode)value.toInt(), command); } break; case TangentsModeRole: { KisKeyframeSP keyframe = channel->keyframeAt(index.column()); if (!keyframe) return false; KisKeyframe::InterpolationTangentsMode mode = (KisKeyframe::InterpolationTangentsMode)value.toInt(); QPointF leftTangent = keyframe->leftTangent(); QPointF rightTangent = keyframe->rightTangent(); if (!command) command = new KUndo2Command(kundo2_i18n("Set interpolation mode")); channel->setInterpolationTangents(keyframe, mode, leftTangent, rightTangent, command); } break; default: return KisTimeBasedItemModel::setData(index, value, role); } if (command && !m_d->undoCommand) { image()->postExecutionUndoAdapter()->addCommand(toQShared(command)); } return true; } QVariant KisAnimationCurvesModel::headerData(int section, Qt::Orientation orientation, int role) const { // TODO return KisTimeBasedItemModel::headerData(section, orientation, role); } void KisAnimationCurvesModel::beginCommand(const KUndo2MagicString &text) { KIS_ASSERT_RECOVER_RETURN(!m_d->undoCommand); m_d->undoCommand = new KUndo2Command(text); } void KisAnimationCurvesModel::endCommand() { KIS_ASSERT_RECOVER_RETURN(m_d->undoCommand); image()->postExecutionUndoAdapter()->addCommand(toQShared(m_d->undoCommand)); m_d->undoCommand = 0; } bool KisAnimationCurvesModel::adjustKeyframes(const QModelIndexList &indexes, int timeOffset, qreal valueOffset) { QScopedPointer command( new KUndo2Command( kundo2_i18np("Adjust Keyframe", "Adjust %1 Keyframes", indexes.size()))); { KisImageBarrierLockerWithFeedback locker(image()); if (timeOffset != 0) { bool ok = createOffsetFramesCommand(indexes, QPoint(timeOffset, 0), false, command.data()); if (!ok) return false; } using KisAnimationUtils::FrameItem; using KisAnimationUtils::FrameItemList; FrameItemList frameItems; Q_FOREACH(QModelIndex index, indexes) { KisScalarKeyframeChannel *channel = m_d->getCurveAt(index)->channel(); KIS_ASSERT_RECOVER_RETURN_VALUE(channel, false); frameItems << FrameItem(channel->node(), channel->id(), index.column() + timeOffset); }; new KisCommandUtils::LambdaCommand( command.data(), [frameItems, valueOffset] () -> KUndo2Command* { QScopedPointer cmd(new KUndo2Command()); bool result = false; Q_FOREACH (const FrameItem &item, frameItems) { const int time = item.time; KisNodeSP node = item.node; KisKeyframeChannel *channel = node->getKeyframeChannel(item.channel); if (!channel) continue; KisKeyframeSP keyframe = channel->keyframeAt(time); if (!keyframe) continue; const qreal currentValue = channel->scalarValue(keyframe); channel->setScalarValue(keyframe, currentValue + valueOffset, cmd.data()); result = true; } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; }); } KisProcessingApplicator::runSingleCommandStroke(image(), command.take(), KisStrokeJobData::BARRIER); return true; } KisAnimationCurve *KisAnimationCurvesModel::addCurve(KisScalarKeyframeChannel *channel) { beginInsertRows(QModelIndex(), m_d->curves.size(), m_d->curves.size()); KisAnimationCurve *curve = new KisAnimationCurve(channel, m_d->chooseNextColor()); m_d->curves.append(curve); endInsertRows(); connect(channel, &KisScalarKeyframeChannel::sigKeyframeAdded, this, &KisAnimationCurvesModel::slotKeyframeChanged); connect(channel, &KisScalarKeyframeChannel::sigKeyframeMoved, this, &KisAnimationCurvesModel::slotKeyframeChanged); connect(channel, &KisScalarKeyframeChannel::sigKeyframeRemoved, this, &KisAnimationCurvesModel::slotKeyframeChanged); connect(channel, &KisScalarKeyframeChannel::sigKeyframeChanged, this, &KisAnimationCurvesModel::slotKeyframeChanged); return curve; } void KisAnimationCurvesModel::removeCurve(KisAnimationCurve *curve) { int index = m_d->curves.indexOf(curve); if (index < 0) return; curve->channel()->disconnect(this); beginRemoveRows(QModelIndex(), index, index); m_d->curves.removeAt(index); delete curve; endRemoveRows(); } void KisAnimationCurvesModel::setCurveVisible(KisAnimationCurve *curve, bool visible) { curve->setVisible(visible); int row = m_d->rowForCurve(curve); emit dataChanged(index(row, 0), index(row, columnCount())); } KisNodeSP KisAnimationCurvesModel::nodeAt(QModelIndex index) const { KisAnimationCurve *curve = m_d->getCurveAt(index); if (curve && curve->channel() && curve->channel()->node()) { return KisNodeSP(curve->channel()->node()); } return 0; } -QList KisAnimationCurvesModel::channelsAt(QModelIndex index) const +QMap KisAnimationCurvesModel::channelsAt(QModelIndex index) const { KisKeyframeChannel *channel = m_d->getCurveAt(index)->channel(); - QList list({channel}); + QMap list; + list[""] = channel; return list; } void KisAnimationCurvesModel::slotKeyframeChanged(KisKeyframeSP keyframe) { int row = m_d->rowForChannel(keyframe->channel()); QModelIndex changedIndex = index(row, keyframe->time()); emit dataChanged(changedIndex, changedIndex); } diff --git a/plugins/dockers/animation/kis_animation_curves_model.h b/plugins/dockers/animation/kis_animation_curves_model.h index 919dcb5fda..f09ee02f20 100644 --- a/plugins/dockers/animation/kis_animation_curves_model.h +++ b/plugins/dockers/animation/kis_animation_curves_model.h @@ -1,98 +1,98 @@ /* * Copyright (c) 2016 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_ANIMATION_CURVES_MODEL_H #define _KIS_ANIMATION_CURVES_MODEL_H #include #include #include #include "kis_time_based_item_model.h" #include "kis_types.h" #include "kundo2command.h" class KisScalarKeyframeChannel; class KisAnimationCurve { public: KisAnimationCurve(KisScalarKeyframeChannel *channel, QColor color); KisScalarKeyframeChannel *channel() const; QColor color() const; void setVisible(bool visible); bool visible() const; private: struct Private; const QScopedPointer m_d; }; class KisAnimationCurvesModel : public KisTimeBasedItemModel { Q_OBJECT public: KisAnimationCurvesModel(QObject *parent); ~KisAnimationCurvesModel() override; bool hasConnectionToCanvas() const; KisAnimationCurve *addCurve(KisScalarKeyframeChannel *channel); void removeCurve(KisAnimationCurve *curve); void setCurveVisible(KisAnimationCurve *curve, bool visible); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; /** * Begins a block of commands. The following calls to setData will be grouped to a single undo step. * Note: MUST be followed by a call to endCommand(). */ void beginCommand(const KUndo2MagicString &text); void endCommand(); bool adjustKeyframes(const QModelIndexList &indexes, int timeOffset, qreal valueOffset); enum ItemDataRole { ScalarValueRole = KisTimeBasedItemModel::UserRole + 101, InterpolationModeRole, TangentsModeRole, LeftTangentRole, RightTangentRole, CurveColorRole, CurveVisibleRole, PreviousKeyframeTime, NextKeyframeTime }; protected: KisNodeSP nodeAt(QModelIndex index) const override; - QList channelsAt(QModelIndex index) const override; + QMap channelsAt(QModelIndex index) const override; private Q_SLOTS: void slotKeyframeChanged(KisKeyframeSP keyframe); private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp index 6376856feb..55b158cdee 100644 --- a/plugins/dockers/animation/kis_time_based_item_model.cpp +++ b/plugins/dockers/animation/kis_time_based_item_model.cpp @@ -1,439 +1,436 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_time_based_item_model.h" #include #include #include "kis_animation_frame_cache.h" #include "kis_animation_player.h" #include "kis_signal_compressor_with_param.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_animation_utils.h" #include "kis_keyframe_channel.h" #include "kis_processing_applicator.h" #include "KisImageBarrierLockerWithFeedback.h" struct KisTimeBasedItemModel::Private { Private() : animationPlayer(0) , numFramesOverride(0) , activeFrameIndex(0) , scrubInProgress(false) , scrubStartFrame(-1) {} KisImageWSP image; KisAnimationFrameCacheWSP framesCache; QPointer animationPlayer; QVector cachedFrames; int numFramesOverride; int activeFrameIndex; bool scrubInProgress; int scrubStartFrame; QScopedPointer > scrubbingCompressor; int baseNumFrames() const { if (image.isNull()) return 0; KisImageAnimationInterface *i = image->animationInterface(); if (!i) return 1; return i->totalLength(); } int effectiveNumFrames() const { if (image.isNull()) return 0; return qMax(baseNumFrames(), numFramesOverride); } int framesPerSecond() { return image->animationInterface()->framerate(); } }; KisTimeBasedItemModel::KisTimeBasedItemModel(QObject *parent) : QAbstractTableModel(parent) , m_d(new Private()) { KisConfig cfg; using namespace std::placeholders; std::function callback( std::bind(&KisTimeBasedItemModel::slotInternalScrubPreviewRequested, this, _1)); m_d->scrubbingCompressor.reset( new KisSignalCompressorWithParam(cfg.scrubbingUpdatesDelay(), callback, KisSignalCompressor::FIRST_ACTIVE)); } KisTimeBasedItemModel::~KisTimeBasedItemModel() {} void KisTimeBasedItemModel::setImage(KisImageWSP image) { KisImageWSP oldImage = m_d->image; m_d->image = image; if (image) { KisImageAnimationInterface *ai = image->animationInterface(); slotCurrentTimeChanged(ai->currentUITime()); connect(ai, SIGNAL(sigFramerateChanged()), SLOT(slotFramerateChanged())); connect(ai, SIGNAL(sigUiTimeChanged(int)), SLOT(slotCurrentTimeChanged(int))); } if (image != oldImage) { reset(); } } void KisTimeBasedItemModel::setFrameCache(KisAnimationFrameCacheSP cache) { if (KisAnimationFrameCacheSP(m_d->framesCache) == cache) return; if (m_d->framesCache) { m_d->framesCache->disconnect(this); } m_d->framesCache = cache; if (m_d->framesCache) { connect(m_d->framesCache, SIGNAL(changed()), SLOT(slotCacheChanged())); } } void KisTimeBasedItemModel::setAnimationPlayer(KisAnimationPlayer *player) { if (m_d->animationPlayer == player) return; if (m_d->animationPlayer) { m_d->animationPlayer->disconnect(this); } m_d->animationPlayer = player; if (m_d->animationPlayer) { connect(m_d->animationPlayer, SIGNAL(sigPlaybackStopped()), SLOT(slotPlaybackStopped())); connect(m_d->animationPlayer, SIGNAL(sigFrameChanged()), SLOT(slotPlaybackFrameChanged())); } } void KisTimeBasedItemModel::setLastVisibleFrame(int time) { const int growThreshold = m_d->effectiveNumFrames() - 3; const int growValue = time + 8; const int shrinkThreshold = m_d->effectiveNumFrames() - 12; const int shrinkValue = qMax(m_d->baseNumFrames(), qMin(growValue, shrinkThreshold)); const bool canShrink = m_d->effectiveNumFrames() > m_d->baseNumFrames(); if (time >= growThreshold) { beginInsertColumns(QModelIndex(), m_d->effectiveNumFrames(), growValue - 1); m_d->numFramesOverride = growValue; endInsertColumns(); } else if (time < shrinkThreshold && canShrink) { beginRemoveColumns(QModelIndex(), shrinkValue, m_d->effectiveNumFrames() - 1); m_d->numFramesOverride = shrinkValue; endRemoveColumns(); } } int KisTimeBasedItemModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_d->effectiveNumFrames(); } QVariant KisTimeBasedItemModel::data(const QModelIndex &index, int role) const { switch (role) { case ActiveFrameRole: { return index.column() == m_d->activeFrameIndex; } } return QVariant(); } bool KisTimeBasedItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; switch (role) { case ActiveFrameRole: { setHeaderData(index.column(), Qt::Horizontal, value, role); break; } } return false; } QVariant KisTimeBasedItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { switch (role) { case ActiveFrameRole: return section == m_d->activeFrameIndex; case FrameCachedRole: return m_d->cachedFrames.size() > section ? m_d->cachedFrames[section] : false; case FramesPerSecondRole: return m_d->framesPerSecond(); } } return QVariant(); } bool KisTimeBasedItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (orientation == Qt::Horizontal) { switch (role) { case ActiveFrameRole: if (value.toBool() && section != m_d->activeFrameIndex) { int prevFrame = m_d->activeFrameIndex; m_d->activeFrameIndex = section; scrubTo(m_d->activeFrameIndex, m_d->scrubInProgress); /** * Optimization Hack Alert: * * ideally, we should emit all four signals, but... The * point is this code is used in a tight loop during * playback, so it should run as fast as possible. To tell * the story short, commenting out these three lines makes * playback run 15% faster ;) */ if (m_d->scrubInProgress) { //emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); //emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); //emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); } else { emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); } } } } return false; } bool KisTimeBasedItemModel::removeFrames(const QModelIndexList &indexes) { KisAnimationUtils::FrameItemList frameItems; { KisImageBarrierLockerWithFeedback locker(m_d->image); Q_FOREACH (const QModelIndex &index, indexes) { int time = index.column(); - - QList channels = channelsAt(index); - Q_FOREACH(KisKeyframeChannel *channel, channels) { + Q_FOREACH(KisKeyframeChannel *channel, channelsAt(index)) { if (channel->keyframeAt(time)) { frameItems << KisAnimationUtils::FrameItem(channel->node(), channel->id(), index.column()); } } } } if (frameItems.isEmpty()) return false; KisAnimationUtils::removeKeyframes(m_d->image, frameItems); return true; } KUndo2Command* KisTimeBasedItemModel::createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, KUndo2Command *parentCommand) { if (srcIndexes.isEmpty()) return 0; if (offset.isNull()) return 0; KisAnimationUtils::sortPointsForSafeMove(&srcIndexes, offset); KisAnimationUtils::FrameItemList srcFrameItems; KisAnimationUtils::FrameItemList dstFrameItems; Q_FOREACH (const QModelIndex &srcIndex, srcIndexes) { QModelIndex dstIndex = index( srcIndex.row() + offset.y(), srcIndex.column() + offset.x()); KisNodeSP srcNode = nodeAt(srcIndex); KisNodeSP dstNode = nodeAt(dstIndex); if (!srcNode || !dstNode) { return 0; } - QList channels = channelsAt(srcIndex); - Q_FOREACH(KisKeyframeChannel *channel, channels) { + Q_FOREACH(KisKeyframeChannel *channel, channelsAt(srcIndex)) { if (channel->keyframeAt(srcIndex.column())) { srcFrameItems << KisAnimationUtils::FrameItem(srcNode, channel->id(), srcIndex.column()); dstFrameItems << KisAnimationUtils::FrameItem(dstNode, channel->id(), dstIndex.column()); } } } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcFrameItems.size() == dstFrameItems.size(), 0); if (srcFrameItems.isEmpty()) return 0; return KisAnimationUtils::createMoveKeyframesCommand(srcFrameItems, dstFrameItems, copyFrames, parentCommand); } bool KisTimeBasedItemModel::offsetFrames(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames) { KUndo2Command *cmd = 0; { KisImageBarrierLockerWithFeedback locker(m_d->image); cmd = createOffsetFramesCommand(srcIndexes, offset, copyFrames); } if (cmd) { KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER); } return cmd; } void KisTimeBasedItemModel::slotInternalScrubPreviewRequested(int time) { if (m_d->animationPlayer && !m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->displayFrame(time); } } void KisTimeBasedItemModel::setScrubState(bool active) { if (!m_d->scrubInProgress && active) { m_d->scrubStartFrame = m_d->activeFrameIndex; m_d->scrubInProgress = true; } if (m_d->scrubInProgress && !active) { m_d->scrubInProgress = false; if (m_d->scrubStartFrame >= 0 && m_d->scrubStartFrame != m_d->activeFrameIndex) { scrubTo(m_d->activeFrameIndex, false); } m_d->scrubStartFrame = -1; } } void KisTimeBasedItemModel::scrubTo(int time, bool preview) { if (m_d->animationPlayer && m_d->animationPlayer->isPlaying()) return; KIS_ASSERT_RECOVER_RETURN(m_d->image); if (preview) { if (m_d->animationPlayer) { m_d->scrubbingCompressor->start(time); } } else { m_d->image->animationInterface()->requestTimeSwitchWithUndo(time); } } void KisTimeBasedItemModel::slotFramerateChanged() { emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); } void KisTimeBasedItemModel::slotCurrentTimeChanged(int time) { if (time != m_d->activeFrameIndex) { setHeaderData(time, Qt::Horizontal, true, ActiveFrameRole); } } void KisTimeBasedItemModel::slotCacheChanged() { const int numFrames = columnCount(); m_d->cachedFrames.resize(numFrames); for (int i = 0; i < numFrames; i++) { m_d->cachedFrames[i] = m_d->framesCache->frameStatus(i) == KisAnimationFrameCache::Cached; } emit headerDataChanged(Qt::Horizontal, 0, numFrames); } void KisTimeBasedItemModel::slotPlaybackFrameChanged() { if (!m_d->animationPlayer->isPlaying()) return; setData(index(0, m_d->animationPlayer->currentTime()), true, ActiveFrameRole); } void KisTimeBasedItemModel::slotPlaybackStopped() { setData(index(0, m_d->image->animationInterface()->currentUITime()), true, ActiveFrameRole); } void KisTimeBasedItemModel::setPlaybackRange(const KisTimeRange &range) { if (m_d->image.isNull()) return; KisImageAnimationInterface *i = m_d->image->animationInterface(); i->setPlaybackRange(range); } bool KisTimeBasedItemModel::isPlaybackActive() const { return m_d->animationPlayer && m_d->animationPlayer->isPlaying(); } int KisTimeBasedItemModel::currentTime() const { return m_d->image->animationInterface()->currentUITime(); } KisImageWSP KisTimeBasedItemModel::image() const { return m_d->image; } diff --git a/plugins/dockers/animation/kis_time_based_item_model.h b/plugins/dockers/animation/kis_time_based_item_model.h index 72cb3382b8..08428c54fa 100644 --- a/plugins/dockers/animation/kis_time_based_item_model.h +++ b/plugins/dockers/animation/kis_time_based_item_model.h @@ -1,98 +1,98 @@ /* * Copyright (c) 2016 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_TIME_BASED_ITEM_MODEL_H #define _KIS_TIME_BASED_ITEM_MODEL_H #include #include #include "kritaanimationdocker_export.h" #include "kis_types.h" class KisTimeRange; class KisAnimationPlayer; class KisKeyframeChannel; class KRITAANIMATIONDOCKER_EXPORT KisTimeBasedItemModel : public QAbstractTableModel { Q_OBJECT public: KisTimeBasedItemModel(QObject *parent); ~KisTimeBasedItemModel() override; void setImage(KisImageWSP image); void setFrameCache(KisAnimationFrameCacheSP cache); void setAnimationPlayer(KisAnimationPlayer *player); void setLastVisibleFrame(int time); int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override; bool removeFrames(const QModelIndexList &indexes); bool offsetFrames(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames); void setScrubState(bool active); void scrubTo(int time, bool preview); void setPlaybackRange(const KisTimeRange &range); bool isPlaybackActive() const; int currentTime() const; enum ItemDataRole { ActiveFrameRole = Qt::UserRole + 101, FrameExistsRole, SpecialKeyframeExists, FrameCachedRole, FrameEditableRole, FramesPerSecondRole, UserRole }; protected: virtual KisNodeSP nodeAt(QModelIndex index) const = 0; - virtual QList channelsAt(QModelIndex index) const = 0; + virtual QMap channelsAt(QModelIndex index) const = 0; KisImageWSP image() const; KUndo2Command* createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, KUndo2Command *parentCommand = 0); private Q_SLOTS: void slotFramerateChanged(); void slotCurrentTimeChanged(int time); void slotCacheChanged(); void slotInternalScrubPreviewRequested(int time); void slotPlaybackFrameChanged(); void slotPlaybackStopped(); private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index f23f5c3b59..e42bb122f5 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,720 +1,717 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_model.h" #include #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" #include struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; - - QList channels = dummy->node()->keyframeChannels(); - - Q_FOREACH(KisKeyframeChannel *channel, channels) { + Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodePropertyListCommand::setNodePropertiesNoUndo(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); return true; } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setUseInTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { /** * The dummy might not exist because the user could (quickly) change * active layer and the list of the nodes in m_d->converter will change. */ KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); return dummy ? dummy->node() : 0; } -QList TimelineFramesModel::channelsAt(QModelIndex index) const +QMap TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { reset(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::processUpdateQueue() { Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); KIS_ASSERT_RECOVER_RETURN(dummy); m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } case KoResourceModel::LargeThumbnailRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); if (!dummy) { return QVariant(); } const int maxSize = 200; QSize size = dummy->node()->extent().size(); size.scale(maxSize, maxSize, Qt::KeepAspectRatio); if (size.width() == 0 || size.height() == 0) { // No thumbnail can be shown if there isn't width or height... return QVariant(); } QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column())); return image; } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedMask()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->useInTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setUseInTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = m_d->lastClickedIndex.row(); const int baseColumn = m_d->lastClickedIndex.column(); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { stream << index.row() - baseRow << index.column() - baseColumn; } data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; const bool copyFrames = action == Qt::CopyAction; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; QModelIndexList srcIndexes; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; int srcRow = baseRow + relRow; int srcColumn = baseColumn + relColumn; srcIndexes << index(srcRow, srcColumn); } const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); return offsetFrames(srcIndexes, offset, copyFrames); } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setUseInTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/dockers/animation/timeline_frames_model.h index 1c39151c3c..af086b1167 100644 --- a/plugins/dockers/animation/timeline_frames_model.h +++ b/plugins/dockers/animation/timeline_frames_model.h @@ -1,132 +1,132 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_FRAMES_MODEL_H #define __TIMELINE_FRAMES_MODEL_H #include #include #include "kritaanimationdocker_export.h" #include "kis_node_model.h" #include "kis_types.h" #include "kis_node.h" #include "timeline_node_list_keeper.h" class KisNodeDummy; class KisDummiesFacadeBase; class KisAnimationPlayer; class KRITAANIMATIONDOCKER_EXPORT TimelineFramesModel : public TimelineNodeListKeeper::ModelWithExternalNotifications { Q_OBJECT public: TimelineFramesModel(QObject *parent); ~TimelineFramesModel() override; bool hasConnectionToCanvas() const; void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image); bool canDropFrameData(const QMimeData *data, const QModelIndex &index); bool insertOtherLayer(int index, int dstRow); int activeLayerRow() const; bool createFrame(const QModelIndex &dstIndex); bool copyFrame(const QModelIndex &dstIndex); QString audioChannelFileName() const; void setAudioChannelFileName(const QString &fileName); bool isAudioMuted() const; void setAudioMuted(bool value); qreal audioVolume() const; void setAudioVolume(qreal value); void setLastClickedIndex(const QModelIndex &index); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override; Qt::DropActions supportedDragActions() const override; Qt::DropActions supportedDropActions() const override; QStringList mimeTypes() const override; QMimeData * mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool insertRows(int row, int count, const QModelIndex &parent) override; bool removeRows(int row, int count, const QModelIndex &parent) override; enum ItemDataRole { ActiveLayerRole = KisTimeBasedItemModel::UserRole, TimelinePropertiesRole, OtherLayersRole, LayerUsedInTimelineRole, FrameColorLabelIndexRole }; // metatype is added by the original implementation typedef KisBaseNode::Property Property; typedef KisBaseNode::PropertyList PropertyList; typedef TimelineNodeListKeeper::OtherLayer OtherLayer; typedef TimelineNodeListKeeper::OtherLayersList OtherLayersList; struct NodeManipulationInterface { virtual ~NodeManipulationInterface() {} virtual KisLayerSP addPaintLayer() const = 0; virtual void removeNode(KisNodeSP node) const = 0; }; /** * NOTE: the model has an ownership over the interface, that is it'll * be deleted automatically later */ void setNodeManipulationInterface(NodeManipulationInterface *iface); protected: KisNodeSP nodeAt(QModelIndex index) const override; - QList channelsAt(QModelIndex index) const override; + QMap channelsAt(QModelIndex index) const override; private Q_SLOTS: void slotDummyChanged(KisNodeDummy *dummy); void processUpdateQueue(); public Q_SLOTS: void slotCurrentNodeChanged(KisNodeSP node); Q_SIGNALS: void requestCurrentNodeChanged(KisNodeSP node); void sigInfiniteTimelineUpdateNeeded(); void sigAudioChannelChanged(); void sigEnsureRowVisible(int row); - + private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_FRAMES_MODEL_H */ diff --git a/plugins/dockers/animation/timeline_node_list_keeper.cpp b/plugins/dockers/animation/timeline_node_list_keeper.cpp index 9ab50a39f6..98b07c8d47 100644 --- a/plugins/dockers/animation/timeline_node_list_keeper.cpp +++ b/plugins/dockers/animation/timeline_node_list_keeper.cpp @@ -1,249 +1,249 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_node_list_keeper.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "timeline_frames_index_converter.h" #include #include #include "kis_keyframe_channel.h" struct TimelineNodeListKeeper::Private { Private(TimelineNodeListKeeper *_q, ModelWithExternalNotifications *_model, KisDummiesFacadeBase *_dummiesFacade) : q(_q), model(_model), dummiesFacade(_dummiesFacade), converter(dummiesFacade) { } TimelineNodeListKeeper *q; ModelWithExternalNotifications *model; KisDummiesFacadeBase *dummiesFacade; TimelineFramesIndexConverter converter; QVector dummiesList; QSignalMapper dummiesUpdateMapper; QSet connectionsSet; void populateDummiesList() { const int rowCount = converter.rowCount(); for (int i = 0; i < rowCount; ++i) { KisNodeDummy *dummy = converter.dummyFromRow(i); dummiesList.append(dummy); tryConnectDummy(dummy); } } void tryConnectDummy(KisNodeDummy *dummy); void disconnectDummy(KisNodeDummy *dummy); }; TimelineNodeListKeeper::TimelineNodeListKeeper(ModelWithExternalNotifications *model, KisDummiesFacadeBase *dummiesFacade) : m_d(new Private(this, model, dummiesFacade)) { KIS_ASSERT_RECOVER_RETURN(m_d->dummiesFacade); connect(m_d->dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)), SLOT(slotEndInsertDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigBeginRemoveDummy(KisNodeDummy*)), SLOT(slotBeginRemoveDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); m_d->populateDummiesList(); connect(&m_d->dummiesUpdateMapper, SIGNAL(mapped(QObject*)), SLOT(slotUpdateDummyContent(QObject*))); } TimelineNodeListKeeper::~TimelineNodeListKeeper() { } KisNodeDummy* TimelineNodeListKeeper::dummyFromRow(int row) { if (row >= 0 && row < m_d->dummiesList.size()) { return m_d->dummiesList[row]; } return 0; } int TimelineNodeListKeeper::rowForDummy(KisNodeDummy *dummy) { return m_d->dummiesList.indexOf(dummy); } int TimelineNodeListKeeper::rowCount() { return m_d->dummiesList.size(); } void TimelineNodeListKeeper::updateActiveDummy(KisNodeDummy *dummy) { bool oldRemoved = false; bool newAdded = false; KisNodeDummy *oldActiveDummy = m_d->converter.activeDummy(); m_d->converter.updateActiveDummy(dummy, &oldRemoved, &newAdded); if (oldRemoved) { slotBeginRemoveDummy(oldActiveDummy); } if (newAdded) { slotEndInsertDummy(dummy); } } void TimelineNodeListKeeper::slotUpdateDummyContent(QObject *_dummy) { KisNodeDummy *dummy = qobject_cast(_dummy); int pos = m_d->converter.rowForDummy(dummy); if (pos < 0) return; QModelIndex index0 = m_d->model->index(pos, 0); QModelIndex index1 = m_d->model->index(pos, m_d->model->columnCount() - 1); m_d->model->callIndexChanged(index0, index1); } void TimelineNodeListKeeper::Private::tryConnectDummy(KisNodeDummy *dummy) { - QList channels = dummy->node()->keyframeChannels(); + QMap channels = dummy->node()->keyframeChannels(); if (channels.isEmpty()) { if (connectionsSet.contains(dummy)) { connectionsSet.remove(dummy); } return; } if (connectionsSet.contains(dummy)) return; Q_FOREACH(KisKeyframeChannel *channel, channels) { connect(channel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), &dummiesUpdateMapper, SLOT(map())); connect(channel, SIGNAL(sigKeyframeAboutToBeRemoved(KisKeyframeSP)), &dummiesUpdateMapper, SLOT(map())); connect(channel, SIGNAL(sigKeyframeMoved(KisKeyframeSP, int)), &dummiesUpdateMapper, SLOT(map())); dummiesUpdateMapper.setMapping(channel, (QObject*)dummy); } connectionsSet.insert(dummy); } void TimelineNodeListKeeper::Private::disconnectDummy(KisNodeDummy *dummy) { if (!connectionsSet.contains(dummy)) return; - QList channels = dummy->node()->keyframeChannels(); + QMap channels = dummy->node()->keyframeChannels(); if (channels.isEmpty()) { if (connectionsSet.contains(dummy)) { connectionsSet.remove(dummy); } return; } Q_FOREACH(KisKeyframeChannel *channel, channels) { channel->disconnect(&dummiesUpdateMapper); } connectionsSet.remove(dummy); } void TimelineNodeListKeeper::slotEndInsertDummy(KisNodeDummy *dummy) { KIS_ASSERT_RECOVER_RETURN(!m_d->dummiesList.contains(dummy)); if (m_d->converter.isDummyVisible(dummy)) { int pos = m_d->converter.rowForDummy(dummy); m_d->model->callBeginInsertRows(QModelIndex(), pos, pos); m_d->dummiesList.insert(pos, 1, dummy); m_d->tryConnectDummy(dummy); m_d->model->callEndInsertRows(); } } void TimelineNodeListKeeper::slotBeginRemoveDummy(KisNodeDummy *dummy) { if (m_d->dummiesList.contains(dummy)) { int pos = m_d->dummiesList.indexOf(dummy); m_d->model->callBeginRemoveRows(QModelIndex(), pos, pos); m_d->disconnectDummy(dummy); m_d->dummiesList.remove(pos); m_d->model->callEndRemoveRows(); } m_d->converter.notifyDummyRemoved(dummy); } void TimelineNodeListKeeper::slotDummyChanged(KisNodeDummy *dummy) { const bool present = m_d->dummiesList.contains(dummy); const bool shouldBe = m_d->converter.isDummyVisible(dummy); m_d->tryConnectDummy(dummy); if (!present && shouldBe) { slotEndInsertDummy(dummy); } else if (present && !shouldBe) { slotBeginRemoveDummy(dummy); } } void findOtherLayers(KisNodeDummy *root, TimelineNodeListKeeper::OtherLayersList *list, const QString &prefix) { KisNodeSP node = root->node(); if (root->parent() && !node->useInTimeline()) { *list << TimelineNodeListKeeper::OtherLayer( QString(prefix + node->name()), root); } KisNodeDummy *dummy = root->lastChild(); while(dummy) { findOtherLayers(dummy, list, prefix + " "); dummy = dummy->prevSibling(); } } TimelineNodeListKeeper::OtherLayersList TimelineNodeListKeeper::otherLayersList() const { OtherLayersList list; findOtherLayers(m_d->dummiesFacade->rootDummy(), &list, ""); return list; } diff --git a/plugins/dockers/defaultdockers/kis_layer_box.cpp b/plugins/dockers/defaultdockers/kis_layer_box.cpp index 218c65011c..1c45264ba5 100644 --- a/plugins/dockers/defaultdockers/kis_layer_box.cpp +++ b/plugins/dockers/defaultdockers/kis_layer_box.cpp @@ -1,924 +1,929 @@ /* * kis_layer_box.cc - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * 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_layer_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 #include #include #include #include #include #include "kis_action.h" #include "kis_action_manager.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_slider_spin_box.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_node_model.h" #include "canvas/kis_canvas2.h" #include "KisDocument.h" #include "kis_dummies_facade_base.h" #include "kis_shape_controller.h" #include "kis_selection_mask.h" #include "kis_config.h" #include "KisView.h" #include "krita_utils.h" #include "sync_button_and_action.h" #include "kis_color_label_selector_widget.h" #include "kis_signals_blocker.h" #include "kis_color_filter_combo.h" #include "kis_node_filter_proxy_model.h" #include "kis_layer_utils.h" #include "ui_wdglayerbox.h" inline void KisLayerBox::connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id) { if (!view || !button) return; KisAction *action = view->actionManager()->actionByName(id); if (!action) return; connect(button, SIGNAL(clicked()), action, SLOT(trigger())); connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool))); } inline void KisLayerBox::addActionToMenu(QMenu *menu, const QString &id) { if (m_canvas) { menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id)); } } KisLayerBox::KisLayerBox() : QDockWidget(i18n("Layers")) , m_canvas(0) , m_wdgLayerBox(new Ui_WdgLayerBox) , m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE) , m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE) { KisConfig cfg; QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_opacityDelayTimer.setSingleShot(true); m_wdgLayerBox->setupUi(mainWidget); connect(m_wdgLayerBox->listLayers, SIGNAL(contextMenuRequested(const QPoint&, const QModelIndex&)), this, SLOT(slotContextMenuRequested(const QPoint&, const QModelIndex&))); connect(m_wdgLayerBox->listLayers, SIGNAL(collapsed(const QModelIndex&)), SLOT(slotCollapsed(const QModelIndex &))); connect(m_wdgLayerBox->listLayers, SIGNAL(expanded(const QModelIndex&)), SLOT(slotExpanded(const QModelIndex &))); connect(m_wdgLayerBox->listLayers, SIGNAL(selectionChanged(const QModelIndexList&)), SLOT(selectionChanged(const QModelIndexList&))); m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer")); m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer")); m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnRaise->setEnabled(false); m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr")); m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setEnabled(false); m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown")); m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties")); m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer")); m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22)); if (cfg.sliderLabels()) { m_wdgLayerBox->opacityLabel->hide(); m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity"))); } m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0); m_wdgLayerBox->doubleOpacity->setSuffix("%"); connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal))); connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged())); connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int))); m_selectOpaque = new KisAction(i18n("&Select Opaque"), this); m_selectOpaque->setActivationFlags(KisAction::ACTIVE_LAYER); m_selectOpaque->setActivationConditions(KisAction::SELECTION_EDITABLE); m_selectOpaque->setObjectName("select_opaque"); connect(m_selectOpaque, SIGNAL(triggered(bool)), this, SLOT(slotSelectOpaque())); m_actions.append(m_selectOpaque); m_newLayerMenu = new QMenu(this); m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu); m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup); m_nodeModel = new KisNodeModel(this); m_filteringModel = new KisNodeFilterProxyModel(this); m_filteringModel->setNodeModel(m_nodeModel); /** * Connect model updateUI() to enable/disable controls. * Note: nodeActivated() is connected separately in setImage(), because * it needs particular order of calls: first the connection to the * node manager should be called, then updateUI() */ connect(m_nodeModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset())); KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this); showGlobalSelectionMask->setObjectName("show-global-selection-mask"); showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE); showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in Layers docker")); showGlobalSelectionMask->setCheckable(true); connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool))); m_actions.append(showGlobalSelectionMask); showGlobalSelectionMask->setChecked(cfg.showGlobalSelection()); m_colorSelector = new KisColorLabelSelectorWidget(this); connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int))); m_colorSelectorAction = new QWidgetAction(this); m_colorSelectorAction->setDefaultWidget(m_colorSelector); connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), &m_colorLabelCompressor, SLOT(start())); m_wdgLayerBox->listLayers->setModel(m_filteringModel); // this connection should be done *after* the setModel() call to // happen later than the internal selection model connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved, this, &KisLayerBox::slotAboutToRemoveRows); connect(m_wdgLayerBox->cmbFilter, SIGNAL(selectedColorsChanged()), SLOT(updateLayerFiltering())); setEnabled(false); connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail())); connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels())); } KisLayerBox::~KisLayerBox() { delete m_wdgLayerBox; } void expandNodesRecursively(KisNodeSP root, QPointer filteringModel, KisNodeView *nodeView) { if (!root) return; if (filteringModel.isNull()) return; if (!nodeView) return; nodeView->blockSignals(true); KisNodeSP node = root->firstChild(); while (node) { QModelIndex idx = filteringModel->indexFromNode(node); if (idx.isValid()) { nodeView->setExpanded(idx, !node->collapsed()); } if (node->childCount() > 0) { expandNodesRecursively(node, filteringModel, nodeView); } node = node->nextSibling(); } nodeView->blockSignals(false); } void KisLayerBox::setMainWindow(KisViewManager* kisview) { m_nodeManager = kisview->nodeManager(); Q_FOREACH (KisAction *action, m_actions) { kisview->actionManager()-> addAction(action->objectName(), action); } connectActionToButton(kisview, m_wdgLayerBox->bnAdd, "add_new_paint_layer"); connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer"); KisActionManager *actionManager = kisview->actionManager(); KisAction *action = actionManager->createAction("RenameCurrentLayer"); Q_ASSERT(action); connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode())); m_propertiesAction = actionManager->createAction("layer_properties"); Q_ASSERT(m_propertiesAction); new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this); connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked())); m_removeAction = actionManager->createAction("remove_layer"); Q_ASSERT(m_removeAction); new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked())); action = actionManager->createAction("move_layer_up"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this); connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked())); action = actionManager->createAction("move_layer_down"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this); connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked())); } void KisLayerBox::setCanvas(KoCanvasBase *canvas) { if (m_canvas == canvas) return; setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0); if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); animation->disconnect(this); } disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); } m_canvas = dynamic_cast(canvas); if (m_canvas) { m_image = m_canvas->image(); connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start())); KisDocument* doc = static_cast(m_canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); KisDummiesFacadeBase *kritaDummiesFacade = static_cast(kritaShapeController); m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_nodeManager->nodeSelectionAdapter(), m_nodeManager->nodeInsertionAdapter()); connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged())); // cold start if (m_nodeManager) { setCurrentNode(m_nodeManager->activeNode()); // Connection KisNodeManager -> KisLayerBox connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(setCurrentNode(KisNodeSP))); connect(m_nodeManager, SIGNAL(sigUiNeedChangeSelectedNodes(const QList &)), SLOT(slotNodeManagerChangedSelection(const QList &))); } else { setCurrentNode(m_canvas->imageView()->currentNode()); } // Connection KisLayerBox -> KisNodeManager (isolate layer) connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()), m_nodeManager, SLOT(toggleIsolateActiveNode())); KisImageAnimationInterface *animation = m_image->animationInterface(); connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &KisLayerBox::slotImageTimeChanged); expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex()); updateAvailableLabels(); addActionToMenu(m_newLayerMenu, "add_new_paint_layer"); addActionToMenu(m_newLayerMenu, "add_new_group_layer"); addActionToMenu(m_newLayerMenu, "add_new_clone_layer"); addActionToMenu(m_newLayerMenu, "add_new_shape_layer"); addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer"); addActionToMenu(m_newLayerMenu, "add_new_fill_layer"); addActionToMenu(m_newLayerMenu, "add_new_file_layer"); m_newLayerMenu->addSeparator(); addActionToMenu(m_newLayerMenu, "add_new_transparency_mask"); addActionToMenu(m_newLayerMenu, "add_new_filter_mask"); addActionToMenu(m_newLayerMenu, "add_new_colorize_mask"); addActionToMenu(m_newLayerMenu, "add_new_transform_mask"); addActionToMenu(m_newLayerMenu, "add_new_selection_mask"); } } void KisLayerBox::unsetCanvas() { setEnabled(false); if (m_canvas) { m_newLayerMenu->clear(); } m_filteringModel->unsetDummiesFacade(); disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); m_canvas = 0; } void KisLayerBox::notifyImageDeleted() { setCanvas(0); } void KisLayerBox::updateUI() { if (!m_canvas) return; if (!m_nodeManager) return; KisNodeSP activeNode = m_nodeManager->activeNode(); if (activeNode != m_activeNode) { if( !m_activeNode.isNull() ) m_activeNode->disconnect(this); m_activeNode = activeNode; if (activeNode) { KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false); if (opacityChannel) { watchOpacityChannel(opacityChannel); } else { watchOpacityChannel(0); connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &KisLayerBox::slotKeyframeChannelAdded); } } } m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false)); if (activeNode) { if (m_nodeManager->activePaintDevice()) { slotFillCompositeOps(m_nodeManager->activeColorSpace()); } else { slotFillCompositeOps(m_image->colorSpace()); } if (activeNode->inherits("KisColorizeMask") || activeNode->inherits("KisLayer")) { m_wdgLayerBox->doubleOpacity->setEnabled(true); slotSetOpacity(activeNode->opacity() * 100.0 / 255); const KoCompositeOp* compositeOp = activeNode->compositeOp(); if (compositeOp) { slotSetCompositeOp(compositeOp); } else { m_wdgLayerBox->cmbComposite->setEnabled(false); } const KisGroupLayer *group = qobject_cast(activeNode.data()); bool compositeSelectionActive = !(group && group->passThroughMode()); m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive); } else if (activeNode->inherits("KisMask")) { m_wdgLayerBox->cmbComposite->setEnabled(false); m_wdgLayerBox->doubleOpacity->setEnabled(false); } } } /** * This method is called *only* when non-GUI code requested the * change of the current node */ void KisLayerBox::setCurrentNode(KisNodeSP node) { m_filteringModel->setActiveNode(node); QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex(); m_filteringModel->setData(index, true, KisNodeModel::ActiveRole); updateUI(); } void KisLayerBox::slotModelReset() { if(m_nodeModel->hasDummiesFacade()) { QItemSelection selection; Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) { const QModelIndex &idx = m_filteringModel->indexFromNode(node); if(idx.isValid()){ QItemSelectionRange selectionRange(idx); selection << selectionRange; } } m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } updateUI(); } void KisLayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp) { KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id()); m_wdgLayerBox->cmbComposite->blockSignals(true); m_wdgLayerBox->cmbComposite->selectCompositeOp(opId); m_wdgLayerBox->cmbComposite->blockSignals(false); } void KisLayerBox::slotFillCompositeOps(const KoColorSpace* colorSpace) { m_wdgLayerBox->cmbComposite->validate(colorSpace); } // range: 0-100 void KisLayerBox::slotSetOpacity(double opacity) { Q_ASSERT(opacity >= 0 && opacity <= 100); m_wdgLayerBox->doubleOpacity->blockSignals(true); m_wdgLayerBox->doubleOpacity->setValue(opacity); m_wdgLayerBox->doubleOpacity->blockSignals(false); } void KisLayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index) { KisNodeList nodes = m_nodeManager->selectedNodes(); KisNodeSP activeNode = m_nodeManager->activeNode(); if (nodes.isEmpty() || !activeNode) return; if (m_canvas) { QMenu menu; const bool singleLayer = nodes.size() == 1; if (index.isValid()) { menu.addAction(m_propertiesAction); if (singleLayer) { addActionToMenu(&menu, "layer_style"); } { KisSignalsBlocker b(m_colorSelector); m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1); } menu.addAction(m_colorSelectorAction); menu.addSeparator(); addActionToMenu(&menu, "cut_layer_clipboard"); addActionToMenu(&menu, "copy_layer_clipboard"); addActionToMenu(&menu, "paste_layer_from_clipboard"); menu.addAction(m_removeAction); addActionToMenu(&menu, "duplicatelayer"); addActionToMenu(&menu, "merge_layer"); if (singleLayer) { addActionToMenu(&menu, "flatten_image"); addActionToMenu(&menu, "flatten_layer"); } menu.addSeparator(); QMenu *selectMenu = menu.addMenu(i18n("&Select")); addActionToMenu(selectMenu, "select_all_layers"); addActionToMenu(selectMenu, "select_visible_layers"); addActionToMenu(selectMenu, "select_invisible_layers"); addActionToMenu(selectMenu, "select_locked_layers"); addActionToMenu(selectMenu, "select_unlocked_layers"); QMenu *groupMenu = menu.addMenu(i18n("&Group")); addActionToMenu(groupMenu, "create_quick_group"); addActionToMenu(groupMenu, "create_quick_clipping_group"); addActionToMenu(groupMenu, "quick_ungroup"); + QMenu *locksMenu = menu.addMenu(i18n("&Locks && visibility")); + addActionToMenu(locksMenu, "toggle_layer_lock"); + addActionToMenu(locksMenu, "toggle_layer_visibility"); + addActionToMenu(locksMenu, "toggle_layer_alpha_lock"); + addActionToMenu(locksMenu, "toggle_layer_inherit_alpha"); if (singleLayer) { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_transparency_mask"); addActionToMenu(addLayerMenu, "add_new_filter_mask"); addActionToMenu(addLayerMenu, "add_new_colorize_mask"); addActionToMenu(addLayerMenu, "add_new_transform_mask"); addActionToMenu(addLayerMenu, "add_new_selection_mask"); QMenu *convertToMenu = menu.addMenu(i18n("&Convert")); addActionToMenu(convertToMenu, "convert_to_paint_layer"); addActionToMenu(convertToMenu, "convert_to_transparency_mask"); addActionToMenu(convertToMenu, "convert_to_filter_mask"); addActionToMenu(convertToMenu, "convert_to_selection_mask"); QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha")); addActionToMenu(splitAlphaMenu, "split_alpha_into_mask"); addActionToMenu(splitAlphaMenu, "split_alpha_write"); addActionToMenu(splitAlphaMenu, "split_alpha_save_merged"); } menu.addSeparator(); if (singleLayer) { addActionToMenu(&menu, "show_in_timeline"); KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node && !node->inherits("KisTransformMask")) { addActionToMenu(&menu, "isolate_layer"); } menu.addAction(m_selectOpaque); } } menu.exec(pos); } } void KisLayerBox::slotMinimalView() { m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::MinimalMode); } void KisLayerBox::slotDetailedView() { m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::DetailedMode); } void KisLayerBox::slotThumbnailView() { m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::ThumbnailMode); } void KisLayerBox::slotRmClicked() { if (!m_canvas) return; m_nodeManager->removeNode(); } void KisLayerBox::slotRaiseClicked() { if (!m_canvas) return; m_nodeManager->raiseNode(); } void KisLayerBox::slotLowerClicked() { if (!m_canvas) return; m_nodeManager->lowerNode(); } void KisLayerBox::slotPropertiesClicked() { if (!m_canvas) return; if (KisNodeSP active = m_nodeManager->activeNode()) { m_nodeManager->nodeProperties(active); } } void KisLayerBox::slotCompositeOpChanged(int index) { Q_UNUSED(index); if (!m_canvas) return; QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id(); m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp)); } void KisLayerBox::slotOpacityChanged() { if (!m_canvas) return; m_blockOpacityUpdate = true; m_nodeManager->nodeOpacityChanged(m_newOpacity, true); m_blockOpacityUpdate = false; } void KisLayerBox::slotOpacitySliderMoved(qreal opacity) { m_newOpacity = opacity; m_opacityDelayTimer.start(200); } void KisLayerBox::slotCollapsed(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(true); } } void KisLayerBox::slotExpanded(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(false); } } void KisLayerBox::slotSelectOpaque() { if (!m_canvas) return; QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque"); if (action) { action->trigger(); } } void KisLayerBox::slotNodeCollapsedChanged() { expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } KisNodeSP KisLayerBox::findNonHidableNode(KisNodeSP startNode) { if (isSelectionMask(startNode) && startNode->parent() && !startNode->parent()->parent()) { KisNodeSP node = startNode->prevSibling(); while (node && isSelectionMask(node)) { node = node->prevSibling(); } if (!node) { node = startNode->nextSibling(); while (node && isSelectionMask(node)) { node = node->nextSibling(); } } if (!node) { node = m_image->root()->lastChild(); while (node && isSelectionMask(node)) { node = node->prevSibling(); } } KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!"); startNode = node; } return startNode; } void KisLayerBox::slotEditGlobalSelection(bool showSelections) { KisNodeSP lastActiveNode = m_nodeManager->activeNode(); KisNodeSP activateNode = lastActiveNode; if (!showSelections) { activateNode = findNonHidableNode(activateNode); } m_nodeModel->setShowGlobalSelection(showSelections); if (showSelections) { KisNodeSP newMask = m_image->rootLayer()->selectionMask(); if (newMask) { activateNode = newMask; } } if (activateNode) { if (lastActiveNode != activateNode) { m_nodeManager->slotNonUiActivatedNode(activateNode); } else { setCurrentNode(lastActiveNode); } } } void KisLayerBox::selectionChanged(const QModelIndexList selection) { if (!m_nodeManager) return; /** * When the user clears the extended selection by clicking on the * empty area of the docker, the selection should be reset on to * the active layer, which might be even unselected(!). */ if (selection.isEmpty() && m_nodeManager->activeNode()) { QModelIndex selectedIndex = m_filteringModel->indexFromNode(m_nodeManager->activeNode()); m_wdgLayerBox->listLayers->selectionModel()-> setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect); return; } QList selectedNodes; Q_FOREACH (const QModelIndex &idx, selection) { selectedNodes << m_filteringModel->nodeFromIndex(idx); } m_nodeManager->slotSetSelectedNodes(selectedNodes); updateUI(); } void KisLayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end) { /** * Qt has changed its behavior when deleting an item. Previously * the selection priority was on the next item in the list, and * now it has shanged to the previous item. Here we just adjust * the selected item after the node removal. Please take care that * this method overrides what was done by the corresponding method * of QItemSelectionModel, which *has already done* its work. That * is why we use (start - 1) and (end + 1) in the activation * condition. * * See bug: https://bugs.kde.org/show_bug.cgi?id=345601 */ QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex(); QAbstractItemModel *model = m_filteringModel; if (currentIndex.isValid() && parent == currentIndex.parent() && currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) { QModelIndex old = currentIndex; if (model && end < model->rowCount(parent) - 1) // there are rows left below the change currentIndex = model->index(end + 1, old.column(), parent); else if (start > 0) // there are rows left above the change currentIndex = model->index(start - 1, old.column(), parent); else // there are no rows left in the table currentIndex = QModelIndex(); if (currentIndex.isValid() && currentIndex != old) { m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex); } } } void KisLayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes) { if (!m_nodeManager) return; QModelIndexList newSelection; Q_FOREACH(KisNodeSP node, nodes) { newSelection << m_filteringModel->indexFromNode(node); } QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel(); if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) { return; } QItemSelection selection; Q_FOREACH(const QModelIndex &idx, newSelection) { selection.select(idx, idx); } model->select(selection, QItemSelectionModel::ClearAndSelect); } void KisLayerBox::updateThumbnail() { m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex()); } void KisLayerBox::slotRenameCurrentNode() { m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex()); } void KisLayerBox::slotColorLabelChanged(int label) { KisNodeList nodes = m_nodeManager->selectedNodes(); Q_FOREACH(KisNodeSP node, nodes) { auto applyLabelFunc = [label](KisNodeSP node) { node->setColorLabelIndex(label); }; KisLayerUtils::recursiveApplyNodes(node, applyLabelFunc); } } void KisLayerBox::updateAvailableLabels() { if (!m_image) return; m_wdgLayerBox->cmbFilter->updateAvailableLabels(m_image->root()); } void KisLayerBox::updateLayerFiltering() { m_filteringModel->setAcceptedLabels(m_wdgLayerBox->cmbFilter->selectedColors()); } void KisLayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel) { if (channel->id() == KisKeyframeChannel::Opacity.id()) { watchOpacityChannel(channel); } } void KisLayerBox::watchOpacityChannel(KisKeyframeChannel *channel) { if (m_opacityChannel) { m_opacityChannel->disconnect(this); } m_opacityChannel = channel; if (m_opacityChannel) { connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); } } void KisLayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe) { Q_UNUSED(keyframe); if (m_blockOpacityUpdate) return; updateUI(); } void KisLayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime) { Q_UNUSED(fromTime); slotOpacityKeyframeChanged(keyframe); } void KisLayerBox::slotImageTimeChanged(int time) { Q_UNUSED(time); updateUI(); } #include "moc_kis_layer_box.cpp" diff --git a/plugins/dockers/griddocker/grid_config_widget.cpp b/plugins/dockers/griddocker/grid_config_widget.cpp index 51010c66db..3dd1cde309 100644 --- a/plugins/dockers/griddocker/grid_config_widget.cpp +++ b/plugins/dockers/griddocker/grid_config_widget.cpp @@ -1,328 +1,336 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; 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 "grid_config_widget.h" #include "ui_grid_config_widget.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_debug.h" #include "kis_aspect_ratio_locker.h" #include "kis_int_parse_spin_box.h" struct GridConfigWidget::Private { Private() : guiSignalsBlocked(false) {} KisGridConfig gridConfig; KisGuidesConfig guidesConfig; bool guiSignalsBlocked; }; GridConfigWidget::GridConfigWidget(QWidget *parent) : QWidget(parent), ui(new Ui::GridConfigWidget), m_d(new Private) { ui->setupUi(this); ui->colorMain->setAlphaChannelEnabled(true); ui->colorSubdivision->setAlphaChannelEnabled(true); ui->colorGuides->setAlphaChannelEnabled(true); ui->angleLeftSpinbox->setSuffix(QChar(Qt::Key_degree)); ui->angleRightSpinbox->setSuffix(QChar(Qt::Key_degree)); ui->cellSpacingSpinbox->setSuffix(i18n(" px")); ui->gridTypeCombobox->addItem(i18n("Rectangle")); ui->gridTypeCombobox->addItem(i18n("Isometric")); ui->gridTypeCombobox->setCurrentIndex(0); // set to rectangle by default slotGridTypeChanged(); // update the UI to hide any elements we don't need connect(ui->gridTypeCombobox, SIGNAL(currentIndexChanged(int)), SLOT(slotGridTypeChanged())); m_isGridEnabled = false; setGridConfig(m_d->gridConfig); setGuidesConfig(m_d->guidesConfig); // hide offset UI elements if offset is disabled connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->lblXOffset, SLOT(setVisible(bool))); connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->lblYOffset, SLOT(setVisible(bool))); connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->intXOffset, SLOT(setVisible(bool))); connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->intYOffset, SLOT(setVisible(bool))); connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->offsetAspectButton, SLOT(setVisible(bool))); ui->lblXOffset->setVisible(false); ui->lblYOffset->setVisible(false); ui->intXOffset->setVisible(false); ui->intYOffset->setVisible(false); ui->offsetAspectButton->setVisible(false); connect(ui->chkShowGrid, SIGNAL(stateChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->chkSnapToGrid, SIGNAL(stateChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->chkShowGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged())); connect(ui->chkSnapToGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged())); connect(ui->chkLockGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged())); connect(ui->intSubdivision, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->angleLeftSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->angleRightSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->cellSpacingSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->selectMainStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->colorMain, SIGNAL(changed(const QColor&)), SLOT(slotGridGuiChanged())); connect(ui->selectSubdivisionStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->colorSubdivision, SIGNAL(changed(const QColor&)), SLOT(slotGridGuiChanged())); connect(ui->selectGuidesStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGuidesGuiChanged())); connect(ui->colorGuides, SIGNAL(changed(const QColor&)), SLOT(slotGuidesGuiChanged())); ui->chkOffset->setChecked(false); KisAspectRatioLocker *offsetLocker = new KisAspectRatioLocker(this); offsetLocker->connectSpinBoxes(ui->intXOffset, ui->intYOffset, ui->offsetAspectButton); KisAspectRatioLocker *spacingLocker = new KisAspectRatioLocker(this); spacingLocker->connectSpinBoxes(ui->intHSpacing, ui->intVSpacing, ui->spacingAspectButton); connect(offsetLocker, SIGNAL(sliderValueChanged()), SLOT(slotGridGuiChanged())); connect(offsetLocker, SIGNAL(aspectButtonChanged()), SLOT(slotGridGuiChanged())); connect(spacingLocker, SIGNAL(sliderValueChanged()), SLOT(slotGridGuiChanged())); connect(spacingLocker, SIGNAL(aspectButtonChanged()), SLOT(slotGridGuiChanged())); + + connect(ui->chkShowRulers,SIGNAL(toggled(bool)),SIGNAL(showRulersChanged(bool))); } GridConfigWidget::~GridConfigWidget() { delete ui; } void GridConfigWidget::setGridConfig(const KisGridConfig &value) { KisGridConfig currentConfig = fetchGuiGridConfig(); if (currentConfig == value) return; setGridConfigImpl(value); } void GridConfigWidget::setGuidesConfig(const KisGuidesConfig &value) { KisGuidesConfig currentConfig = fetchGuiGuidesConfig(); if (currentConfig == value) return; setGuidesConfigImpl(value); } void GridConfigWidget::setGridConfigImpl(const KisGridConfig &value) { m_d->gridConfig = value; m_d->guiSignalsBlocked = true; ui->offsetAspectButton->setKeepAspectRatio(m_d->gridConfig.offsetAspectLocked()); ui->spacingAspectButton->setKeepAspectRatio(m_d->gridConfig.spacingAspectLocked()); ui->chkShowGrid->setChecked(m_d->gridConfig.showGrid()); ui->intHSpacing->setValue(m_d->gridConfig.spacing().x()); ui->intVSpacing->setValue(m_d->gridConfig.spacing().y()); ui->intXOffset->setValue(m_d->gridConfig.offset().x()); ui->intYOffset->setValue(m_d->gridConfig.offset().y()); ui->intSubdivision->setValue(m_d->gridConfig.subdivision()); ui->chkSnapToGrid->setChecked(m_d->gridConfig.snapToGrid()); ui->angleLeftSpinbox->setValue(m_d->gridConfig.angleLeft()); ui->angleRightSpinbox->setValue(m_d->gridConfig.angleRight()); ui->cellSpacingSpinbox->setValue(m_d->gridConfig.cellSpacing()); ui->selectMainStyle->setCurrentIndex(int(m_d->gridConfig.lineTypeMain())); ui->selectSubdivisionStyle->setCurrentIndex(int(m_d->gridConfig.lineTypeSubdivision())); ui->gridTypeCombobox->setCurrentIndex(m_d->gridConfig.gridType()); ui->colorMain->setColor(m_d->gridConfig.colorMain()); ui->colorSubdivision->setColor(m_d->gridConfig.colorSubdivision()); m_d->guiSignalsBlocked = false; emit gridValueChanged(); } void GridConfigWidget::setGuidesConfigImpl(const KisGuidesConfig &value) { m_d->guidesConfig = value; m_d->guiSignalsBlocked = true; ui->chkShowGuides->setChecked(m_d->guidesConfig.showGuides()); ui->chkSnapToGuides->setChecked(m_d->guidesConfig.snapToGuides()); ui->chkLockGuides->setChecked(m_d->guidesConfig.lockGuides()); ui->selectGuidesStyle->setCurrentIndex(int(m_d->guidesConfig.guidesLineType())); ui->colorGuides->setColor(m_d->guidesConfig.guidesColor()); m_d->guiSignalsBlocked = false; emit guidesValueChanged(); } KisGridConfig GridConfigWidget::gridConfig() const { return m_d->gridConfig; } KisGuidesConfig GridConfigWidget::guidesConfig() const { return m_d->guidesConfig; } void GridConfigWidget::setGridDivision(int w, int h) { ui->intHSpacing->setMaximum(w); ui->intVSpacing->setMaximum(h); } KisGridConfig GridConfigWidget::fetchGuiGridConfig() const { KisGridConfig config; config.setShowGrid(ui->chkShowGrid->isChecked()); config.setSnapToGrid(ui->chkSnapToGrid->isChecked()); QPoint pt; pt.rx() = ui->intHSpacing->value(); pt.ry() = ui->intVSpacing->value(); config.setSpacing(pt); pt.rx() = ui->intXOffset->value(); pt.ry() = ui->intYOffset->value(); config.setOffset(pt); config.setSubdivision(ui->intSubdivision->value()); config.setAngleLeft(ui->angleLeftSpinbox->value()); config.setAngleRight(ui->angleRightSpinbox->value()); config.setCellSpacing(ui->cellSpacingSpinbox->value()); config.setGridType(ui->gridTypeCombobox->currentIndex()); config.setOffsetAspectLocked(ui->offsetAspectButton->keepAspectRatio()); config.setSpacingAspectLocked(ui->spacingAspectButton->keepAspectRatio()); config.setLineTypeMain(KisGridConfig::LineTypeInternal(ui->selectMainStyle->currentIndex())); config.setLineTypeSubdivision(KisGridConfig::LineTypeInternal(ui->selectSubdivisionStyle->currentIndex())); config.setColorMain(ui->colorMain->color()); config.setColorSubdivision(ui->colorSubdivision->color()); return config; } KisGuidesConfig GridConfigWidget::fetchGuiGuidesConfig() const { KisGuidesConfig config = m_d->guidesConfig; config.setShowGuides(ui->chkShowGuides->isChecked()); config.setSnapToGuides(ui->chkSnapToGuides->isChecked()); config.setLockGuides(ui->chkLockGuides->isChecked()); config.setGuidesLineType(KisGuidesConfig::LineTypeInternal(ui->selectGuidesStyle->currentIndex())); config.setGuidesColor(ui->colorGuides->color()); return config; } void GridConfigWidget::slotGridGuiChanged() { if (m_d->guiSignalsBlocked) return; KisGridConfig currentConfig = fetchGuiGridConfig(); if (currentConfig == m_d->gridConfig) return; setGridConfigImpl(currentConfig); } void GridConfigWidget::slotGuidesGuiChanged() { if (m_d->guiSignalsBlocked) return; KisGuidesConfig currentConfig = fetchGuiGuidesConfig(); if (currentConfig == m_d->guidesConfig) return; setGuidesConfigImpl(currentConfig); } void GridConfigWidget::slotGridTypeChanged() { bool showRectangleControls = ui->gridTypeCombobox->currentIndex() == 0; // specific rectangle UI controls ui->lblXSpacing->setVisible(showRectangleControls); ui->lblYSpacing->setVisible(showRectangleControls); ui->intHSpacing->setVisible(showRectangleControls); ui->intVSpacing->setVisible(showRectangleControls); ui->spacingAspectButton->setVisible(showRectangleControls); ui->lblSubdivision->setVisible(showRectangleControls); ui->intSubdivision->setVisible(showRectangleControls); ui->lblSubdivisionStyle->setVisible(showRectangleControls); ui->selectSubdivisionStyle->setVisible(showRectangleControls); ui->colorSubdivision->setVisible(showRectangleControls); // specific isometric UI controls ui->leftAngleLabel->setVisible(!showRectangleControls); ui->rightAngleLabel->setVisible(!showRectangleControls); ui->angleLeftSpinbox->setVisible(!showRectangleControls); ui->angleRightSpinbox->setVisible(!showRectangleControls); ui->cellSpacingLabel->setVisible(!showRectangleControls); ui->cellSpacingSpinbox->setVisible(!showRectangleControls); // disable snapping for isometric grid type for now // remember if we had snapping enabled if it was on the rectangule mode if (!showRectangleControls) { m_isGridEnabled = ui->chkSnapToGrid->isChecked(); ui->chkSnapToGrid->setEnabled(false); ui->chkSnapToGrid->setChecked(false); } else { ui->chkSnapToGrid->setEnabled(true); ui->chkSnapToGrid->setChecked(m_isGridEnabled); } slotGridGuiChanged(); } +bool GridConfigWidget::showRulers() const +{ + return ui->chkShowRulers->isChecked(); +} - - +void GridConfigWidget::setShowRulers(bool value) +{ + ui->chkShowRulers->setChecked(value); +} diff --git a/plugins/dockers/griddocker/grid_config_widget.h b/plugins/dockers/griddocker/grid_config_widget.h index 7532dcc580..d0d20752f4 100644 --- a/plugins/dockers/griddocker/grid_config_widget.h +++ b/plugins/dockers/griddocker/grid_config_widget.h @@ -1,72 +1,78 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; 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 GRID_CONFIG_WIDGET_H #define GRID_CONFIG_WIDGET_H #include #include namespace Ui { class GridConfigWidget; } class KisGridConfig; class KisGuidesConfig; class GridConfigWidget : public QWidget { Q_OBJECT public: explicit GridConfigWidget(QWidget *parent = 0); ~GridConfigWidget() override; void setGridConfig(const KisGridConfig &value); KisGridConfig gridConfig() const; void setGuidesConfig(const KisGuidesConfig &value); KisGuidesConfig guidesConfig() const; void setGridDivision(int w, int h); + bool showRulers() const; + +public Q_SLOTS: + void setShowRulers(bool value); + private Q_SLOTS: void slotGridGuiChanged(); void slotGuidesGuiChanged(); void slotGridTypeChanged(); Q_SIGNALS: void gridValueChanged(); void guidesValueChanged(); + void showRulersChanged(bool); private: KisGridConfig fetchGuiGridConfig() const; void setGridConfigImpl(const KisGridConfig &value); KisGuidesConfig fetchGuiGuidesConfig() const; void setGuidesConfigImpl(const KisGuidesConfig &value); private: Ui::GridConfigWidget *ui; struct Private; const QScopedPointer m_d; bool m_isGridEnabled; }; #endif // GRID_CONFIG_WIDGET_H diff --git a/plugins/dockers/griddocker/grid_config_widget.ui b/plugins/dockers/griddocker/grid_config_widget.ui index bee6e42556..d6a24494c7 100644 --- a/plugins/dockers/griddocker/grid_config_widget.ui +++ b/plugins/dockers/griddocker/grid_config_widget.ui @@ -1,716 +1,723 @@ GridConfigWidget 0 0 258 527 30 0 0 0 187 280 - 0 + 1 Grid QLayout::SetDefaultConstraint 0 0 Show grid 0 0 Snap to grid 0 0 Type: 0 0 QLayout::SetDefaultConstraint 0 0 10 1000 30 0 0 0 89 0 0 Cell Spacing: 0 0 0 89 1 30 0 0 Right Angle: 0 0 Left Angle: 0 0 X spacing: px 1 500 10 0 0 Y spacing: px 1 500 10 Subdivision: 0 0 1 10 2 0 0 30 0 0 0 0 0 0 0 0 0 Div Style: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 1 Lines Dashed Dots 0 0 30 0 0 Lines Dashed Dots 0 0 Main Style: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 107 22 0 0 Grid Offset false 0 0 px 500 0 0 0 X offset: 0 0 Y offset: 0 0 px 500 0 0 0 24 24 Qt::Vertical 20 40 Guides Show guides Snap to guides Lock guides + + + + Show rulers + + + 0 0 Guides: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 Lines Dashed Dots 0 0 30 0 0 0 0 0 0 0 Qt::Vertical 20 40 KoAspectButton QWidget
KoAspectButton.h
1
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KColorButton QPushButton
kcolorbutton.h
1
KComboBox QComboBox
kcombobox.h
diff --git a/plugins/dockers/griddocker/griddocker_dock.cpp b/plugins/dockers/griddocker/griddocker_dock.cpp index 2332a7b1a6..797cc42173 100644 --- a/plugins/dockers/griddocker/griddocker_dock.cpp +++ b/plugins/dockers/griddocker/griddocker_dock.cpp @@ -1,111 +1,118 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; 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 "griddocker_dock.h" //#include "gridwidget.h" // #include // #include #include #include #include "kis_canvas2.h" #include #include #include "kis_image.h" #include "kis_paint_device.h" #include "kis_signal_compressor.h" #include "grid_config_widget.h" #include "kis_grid_manager.h" #include "kis_grid_config.h" #include "kis_guides_manager.h" #include "kis_guides_config.h" +#include "kis_action.h" GridDockerDock::GridDockerDock( ) : QDockWidget(i18n("Grid and Guides")) , m_canvas(0) { m_configWidget = new GridConfigWidget(this); connect(m_configWidget, SIGNAL(gridValueChanged()), SLOT(slotGuiGridConfigChanged())); connect(m_configWidget, SIGNAL(guidesValueChanged()), SLOT(slotGuiGuidesConfigChanged())); setWidget(m_configWidget); setEnabled(m_canvas); } GridDockerDock::~GridDockerDock() { } void GridDockerDock::setCanvas(KoCanvasBase * canvas) { if(canvas && m_canvas == canvas) return; if (m_canvas) { m_canvasConnections.clear(); m_canvas->disconnectCanvasObserver(this); m_canvas->image()->disconnect(this); } m_canvas = canvas ? dynamic_cast(canvas) : 0; setEnabled(m_canvas); if (m_canvas) { m_canvasConnections.addConnection( m_canvas->viewManager()->gridManager(), SIGNAL(sigRequestUpdateGridConfig(const KisGridConfig&)), this, SLOT(slotGridConfigUpdateRequested(const KisGridConfig&))); + KisAction* action = m_canvas->viewManager()->actionManager()->actionByName("view_ruler"); + + m_canvasConnections.addConnection(m_configWidget,SIGNAL(showRulersChanged(bool)),action,SLOT(setChecked(bool))); + m_canvasConnections.addConnection(action,SIGNAL(toggled(bool)),m_configWidget,SLOT(setShowRulers(bool))); + m_configWidget->setShowRulers(action->isChecked()); + m_canvasConnections.addConnection( m_canvas->viewManager()->guidesManager(), SIGNAL(sigRequestUpdateGuidesConfig(const KisGuidesConfig&)), this, SLOT(slotGuidesConfigUpdateRequested(const KisGuidesConfig&))); QRect rc = m_canvas->image()->bounds(); m_configWidget->setGridDivision(rc.width() / 2, rc.height() / 2); } } void GridDockerDock::unsetCanvas() { setCanvas(0); } void GridDockerDock::slotGuiGridConfigChanged() { if (!m_canvas) return; m_canvas->viewManager()->gridManager()->setGridConfig(m_configWidget->gridConfig()); } void GridDockerDock::slotGridConfigUpdateRequested(const KisGridConfig &config) { m_configWidget->setGridConfig(config); } void GridDockerDock::slotGuiGuidesConfigChanged() { if (!m_canvas) return; m_canvas->viewManager()->guidesManager()->setGuidesConfig(m_configWidget->guidesConfig()); } void GridDockerDock::slotGuidesConfigUpdateRequested(const KisGuidesConfig &config) { m_configWidget->setGuidesConfig(config); } diff --git a/plugins/dockers/shapedockers/CMakeLists.txt b/plugins/dockers/shapedockers/CMakeLists.txt index 64825c8865..5960b7e3fc 100644 --- a/plugins/dockers/shapedockers/CMakeLists.txt +++ b/plugins/dockers/shapedockers/CMakeLists.txt @@ -1,14 +1,20 @@ project(calligradockers) set(calligradockers_SRCS - shapecollection/ShapeCollectionDocker.cpp - shapecollection/CollectionItemModel.cpp + ShapeCollectionDocker.cpp + CollectionItemModel.cpp + SvgSymbolCollectionDocker.cpp Plugin.cpp ) +ki18n_wrap_ui(calligradockers_SRCS + WdgSvgCollection.ui +) + + add_library(krita_docker_defaults MODULE ${calligradockers_SRCS}) -target_link_libraries(krita_docker_defaults kritawidgets) +target_link_libraries(krita_docker_defaults kritawidgets kritaui) install(TARGETS krita_docker_defaults DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/shapedockers/shapecollection/CollectionItemModel.cpp b/plugins/dockers/shapedockers/CollectionItemModel.cpp similarity index 100% rename from plugins/dockers/shapedockers/shapecollection/CollectionItemModel.cpp rename to plugins/dockers/shapedockers/CollectionItemModel.cpp diff --git a/plugins/dockers/shapedockers/shapecollection/CollectionItemModel.h b/plugins/dockers/shapedockers/CollectionItemModel.h similarity index 100% rename from plugins/dockers/shapedockers/shapecollection/CollectionItemModel.h rename to plugins/dockers/shapedockers/CollectionItemModel.h diff --git a/plugins/dockers/shapedockers/Plugin.cpp b/plugins/dockers/shapedockers/Plugin.cpp index b9f6fe9fa3..b0f710cd27 100644 --- a/plugins/dockers/shapedockers/Plugin.cpp +++ b/plugins/dockers/shapedockers/Plugin.cpp @@ -1,36 +1,39 @@ /* This file is part of the KDE project - * Copyright (C) 2007 Thomas Zander + * + * Copyright (C) 2017 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 "Plugin.h" -#include "shapecollection/ShapeCollectionDocker.h" +#include "ShapeCollectionDocker.h" +#include "SvgSymbolCollectionDocker.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(PluginFactory, "calligra_docker_defaults.json", registerPlugin();) Plugin::Plugin(QObject *parent, const QVariantList &) : QObject(parent) { Q_UNUSED(parent); KoDockRegistry::instance()->add(new ShapeCollectionDockerFactory()); + KoDockRegistry::instance()->add(new SvgSymbolCollectionDockerFactory()); } #include diff --git a/plugins/dockers/shapedockers/ShapeCollectionDocker.cpp b/plugins/dockers/shapedockers/ShapeCollectionDocker.cpp new file mode 100644 index 0000000000..ebcd70b87e --- /dev/null +++ b/plugins/dockers/shapedockers/ShapeCollectionDocker.cpp @@ -0,0 +1,171 @@ +/* This file is part of the KDE project + * Copyright (C) 2008 Peter Simonsson + * + * 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 "ShapeCollectionDocker.h" + +#include "CollectionItemModel.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 + +//This class is needed so that the menu returns a sizehint based on the layout and not on the number (0) of menu items +class CollectionMenu : public QMenu +{ +public: + CollectionMenu(QWidget *parent = 0); + QSize sizeHint() const override; +}; + +CollectionMenu::CollectionMenu(QWidget *parent) + : QMenu(parent) +{ +} +QSize CollectionMenu::sizeHint() const +{ + return layout()->sizeHint(); +} + +// +// ShapeCollectionDockerFactory +// + +ShapeCollectionDockerFactory::ShapeCollectionDockerFactory() + : KoDockFactoryBase() +{ +} + +QString ShapeCollectionDockerFactory::id() const +{ + return QString("ShapeCollectionDocker"); +} + +QDockWidget *ShapeCollectionDockerFactory::createDockWidget() +{ + ShapeCollectionDocker *docker = new ShapeCollectionDocker(); + + return docker; +} + + +// +// ShapeCollectionDocker +// +ShapeCollectionDocker::ShapeCollectionDocker(QWidget *parent) + : QDockWidget(parent) +{ + setWindowTitle(i18n("Add Shape")); + + m_quickView = new QListView(this); + m_quickView->setViewMode(QListView::IconMode); + m_quickView->setDragDropMode(QListView::DragOnly); + m_quickView->setSelectionMode(QListView::SingleSelection); + m_quickView->setResizeMode(QListView::Adjust); + m_quickView->setFlow(QListView::LeftToRight); + m_quickView->setGridSize(QSize(128, 128)); + m_quickView->setTextElideMode(Qt::ElideNone); + m_quickView->setWordWrap(true); + setWidget(m_quickView); + + // Load the default shapes and add them to the combobox + loadDefaultShapes(); +} + +void ShapeCollectionDocker::setCanvas(KoCanvasBase *canvas) +{ + setEnabled(canvas != 0); +} + +void ShapeCollectionDocker::unsetCanvas() +{ + setEnabled(false); +} + +void ShapeCollectionDocker::loadDefaultShapes() +{ + QList quicklist; + + Q_FOREACH (const QString &id, KoShapeRegistry::instance()->keys()) { + KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); + // don't show hidden factories + if (factory->hidden()) { + continue; + } + bool oneAdded = false; + + Q_FOREACH (const KoShapeTemplate &shapeTemplate, factory->templates()) { + + oneAdded = true; + KoCollectionItem temp; + temp.id = shapeTemplate.id; + temp.name = shapeTemplate.name; + temp.toolTip = shapeTemplate.toolTip; + temp.icon = KisIconUtils::loadIcon(shapeTemplate.iconName); + temp.properties = shapeTemplate.properties; + quicklist.append(temp); + + QString id = temp.id; + if (!shapeTemplate.templateId.isEmpty()) { + id += '_' + shapeTemplate.templateId; + } + + } + + if (!oneAdded) { + KoCollectionItem temp; + temp.id = factory->id(); + temp.name = factory->name(); + temp.toolTip = factory->toolTip(); + temp.icon = KisIconUtils::loadIcon(factory->iconName()); + temp.properties = 0; + quicklist.append(temp); + } + } + + CollectionItemModel *quickModel = new CollectionItemModel(this); + quickModel->setShapeTemplateList(quicklist); + m_quickView->setModel(quickModel); + +} diff --git a/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.h b/plugins/dockers/shapedockers/ShapeCollectionDocker.h similarity index 57% rename from plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.h rename to plugins/dockers/shapedockers/ShapeCollectionDocker.h index 0fe04d0fae..2c70233d93 100644 --- a/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.h +++ b/plugins/dockers/shapedockers/ShapeCollectionDocker.h @@ -1,124 +1,77 @@ /* This file is part of the KDE project * Copyright (C) 2008 Peter Simonsson * * 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 KOSHAPECOLLECTIONDOCKER_H #define KOSHAPECOLLECTIONDOCKER_H #include #include #include #include #include #include class ShapeCollectionDockerFactory : public KoDockFactoryBase { public: ShapeCollectionDockerFactory(); QString id() const override; QDockWidget *createDockWidget() override; DockPosition defaultDockPosition() const override { return DockRight; } }; class CollectionItemModel; class KoShape; class QListView; class QListWidget; class QListWidgetItem; class QToolButton; class QMenu; class QSpacerItem; class QGridLayout; class ShapeCollectionDocker : public QDockWidget, public KoCanvasObserverBase { Q_OBJECT public: explicit ShapeCollectionDocker(QWidget *parent = 0); /// reimplemented void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; -protected Q_SLOTS: - /** - * Activates the shape creation tool when a shape is selected. - */ - void activateShapeCreationTool(const QModelIndex &index); - void activateShapeCreationToolFromQuick(const QModelIndex &index); - - /** - * Changes the current shape collection - */ - void activateShapeCollection(QListWidgetItem *item); - - /** - * Called when a collection is added from the add collection menu - */ - void loadCollection(); - - /// Called when the close collection button is clicked - void removeCurrentCollection(); - - /// Called when the docker changes area - void locationChanged(Qt::DockWidgetArea area); - protected: /** - * Load the default calligra shapes - */ + * Load the default calligra shapes + */ void loadDefaultShapes(); - /** - * Add a collection to the docker - */ - bool addCollection(const QString &id, const QString &title, CollectionItemModel *model); - void removeCollection(const QString &id); - - /** - * Builds the menu for the Add Collection Button - */ - void buildAddCollectionMenu(); - - /// Generate an icon from @p shape - QIcon generateShapeIcon(KoShape *shape); - private: - void scanCollectionDir(const QString &dirName, QMenu *menu); -private: QListView *m_quickView; - QToolButton *m_moreShapes; - QMenu *m_moreShapesContainer; - QListWidget *m_collectionChooser; - QListView *m_collectionView; - QToolButton *m_closeCollectionButton; - QToolButton *m_addCollectionButton; - QSpacerItem *m_spacer; - QGridLayout *m_layout; QMap m_modelMap; }; #endif //KOSHAPECOLLECTIONDOCKER_H diff --git a/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.cpp b/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.cpp new file mode 100644 index 0000000000..a437720d22 --- /dev/null +++ b/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.cpp @@ -0,0 +1,208 @@ +/* This file is part of the KDE project + * Copyright (C) 2008 Peter Simonsson + * + * 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 "SvgSymbolCollectionDocker.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "ui_WdgSvgCollection.h" + +#include + +// +// SvgCollectionModel +// +SvgCollectionModel::SvgCollectionModel(QObject *parent) + : QAbstractListModel(parent) +{ + setSupportedDragActions(Qt::CopyAction); +} + +QVariant SvgCollectionModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() > m_symbolCollection->symbols().count()) { + return QVariant(); + } + + switch (role) { + case Qt::ToolTipRole: + return m_symbolCollection->symbols()[index.row()]->title; + + case Qt::DecorationRole: + { + QPixmap px = QPixmap::fromImage(m_symbolCollection->symbols()[index.row()]->icon); + QIcon icon(px); + return icon; + } + case Qt::UserRole: + return m_symbolCollection->symbols()[index.row()]->id; + + case Qt::DisplayRole: + return m_symbolCollection->symbols()[index.row()]->title; + + default: + return QVariant(); + } + + return QVariant(); +} + +int SvgCollectionModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_symbolCollection->symbols().count(); +} + +QMimeData *SvgCollectionModel::mimeData(const QModelIndexList &indexes) const +{ + if (indexes.isEmpty()) { + return 0; + } + + QModelIndex index = indexes.first(); + + if (!index.isValid()) { + return 0; + } + + if (m_symbolCollection->symbols().isEmpty()) { + return 0; + } + + QList shapes; + shapes.append(m_symbolCollection->symbols()[index.row()]->shape); + KoDrag drag; + drag.setSvg(shapes); + QMimeData *mimeData = drag.mimeData(); + + return mimeData; +} + +QStringList SvgCollectionModel::mimeTypes() const +{ + return QStringList() << SHAPETEMPLATE_MIMETYPE << "image/svg+xml"; +} + +Qt::ItemFlags SvgCollectionModel::flags(const QModelIndex &index) const +{ + if (index.isValid()) { + return QAbstractListModel::flags(index) | Qt::ItemIsDragEnabled; + } + return QAbstractListModel::flags(index); +} + +void SvgCollectionModel::setSvgSymbolCollectionResource(KoSvgSymbolCollectionResource *resource) +{ + m_symbolCollection = resource; +} + + + +// +// SvgSymbolCollectionDockerFactory +// + +SvgSymbolCollectionDockerFactory::SvgSymbolCollectionDockerFactory() + : KoDockFactoryBase() +{ +} + +QString SvgSymbolCollectionDockerFactory::id() const +{ + return QString("SvgSymbolCollectionDocker"); +} + +QDockWidget *SvgSymbolCollectionDockerFactory::createDockWidget() +{ + SvgSymbolCollectionDocker *docker = new SvgSymbolCollectionDocker(); + + return docker; +} + +// +// SvgSymbolCollectionDocker +// + +SvgSymbolCollectionDocker::SvgSymbolCollectionDocker(QWidget *parent) + : QDockWidget(parent) + , m_wdgSvgCollection(new Ui_WdgSvgCollection()) +{ + setWindowTitle(i18n("Vector Libraries")); + QWidget* mainWidget = new QWidget(this); + setWidget(mainWidget); + m_wdgSvgCollection->setupUi(mainWidget); + + connect(m_wdgSvgCollection->cmbCollections, SIGNAL(activated(int)), SLOT(collectionActivated(int))); + + KoResourceServer *svgCollectionProvider = KoResourceServerProvider::instance()->svgSymbolCollectionServer(); + Q_FOREACH(KoSvgSymbolCollectionResource *r, svgCollectionProvider->resources()) { + m_wdgSvgCollection->cmbCollections->addSqueezedItem(r->name()); + SvgCollectionModel *model = new SvgCollectionModel(); + model->setSvgSymbolCollectionResource(r); + m_models.append(model); + } + + m_wdgSvgCollection->listCollection->setDragEnabled(true); + m_wdgSvgCollection->listCollection->setDragDropMode(QAbstractItemView::DragOnly); + m_wdgSvgCollection->listCollection->setSelectionMode(QListView::SingleSelection); + + KConfigGroup cfg = KSharedConfig::openConfig()->group("SvgSymbolCollection"); + int i = cfg.readEntry("currentCollection", 0); + if (i > m_wdgSvgCollection->cmbCollections->count()) { + i = 0; + } + m_wdgSvgCollection->cmbCollections->setCurrentIndex(i); + collectionActivated(i); +} + +void SvgSymbolCollectionDocker::setCanvas(KoCanvasBase *canvas) +{ + setEnabled(canvas != 0); +} + +void SvgSymbolCollectionDocker::unsetCanvas() +{ + setEnabled(false); +} + +void SvgSymbolCollectionDocker::collectionActivated(int index) +{ + if (index < m_models.size()) { + KConfigGroup cfg = KSharedConfig::openConfig()->group("SvgSymbolCollection"); + cfg.writeEntry("currentCollection", index); + m_wdgSvgCollection->listCollection->setModel(m_models[index]); + } + +} diff --git a/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.h b/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.h new file mode 100644 index 0000000000..bb349bc0ac --- /dev/null +++ b/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.h @@ -0,0 +1,86 @@ +/* This file is part of the KDE project + * Copyright (C) 2017 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 SVGSYMBOLCOLLECTIONDOCKER_H +#define SVGSYMBOLCOLLECTIONDOCKER_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "ui_WdgSvgCollection.h" + +class KoSvgSymbolCollectionResource; + +class SvgCollectionModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit SvgCollectionModel(QObject *parent = 0); + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + QStringList mimeTypes() const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; +public: + void setSvgSymbolCollectionResource(KoSvgSymbolCollectionResource *resource); +private: + KoSvgSymbolCollectionResource *m_symbolCollection; +}; + + +class SvgSymbolCollectionDockerFactory : public KoDockFactoryBase +{ +public: + SvgSymbolCollectionDockerFactory(); + + QString id() const override; + QDockWidget *createDockWidget() override; + DockPosition defaultDockPosition() const override + { + return DockRight; + } +}; + +class SvgSymbolCollectionDocker : public QDockWidget, public KoCanvasObserverBase +{ + Q_OBJECT +public: + + explicit SvgSymbolCollectionDocker(QWidget *parent = 0); + + /// reimplemented + void setCanvas(KoCanvasBase *canvas) override; + void unsetCanvas() override; + +private Q_SLOTS: + + void collectionActivated(int index); + +private: + + Ui_WdgSvgCollection *m_wdgSvgCollection; + QVector m_models; +}; + +#endif //KOSHAPECOLLECTIONDOCKER_H diff --git a/plugins/dockers/shapedockers/WdgSvgCollection.ui b/plugins/dockers/shapedockers/WdgSvgCollection.ui new file mode 100644 index 0000000000..77f90beafa --- /dev/null +++ b/plugins/dockers/shapedockers/WdgSvgCollection.ui @@ -0,0 +1,31 @@ + + + WdgSvgCollection + + + + 0 + 0 + 400 + 300 + + + + + + + + + + + + + + SqueezedComboBox + QComboBox +
squeezedcombobox.h
+
+
+ + +
diff --git a/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.cpp b/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.cpp deleted file mode 100644 index ff3a8cf707..0000000000 --- a/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.cpp +++ /dev/null @@ -1,516 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2008 Peter Simonsson - * - * 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 "ShapeCollectionDocker.h" - -#include "CollectionItemModel.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 - -//This class is needed so that the menu returns a sizehint based on the layout and not on the number (0) of menu items -class CollectionMenu : public QMenu -{ -public: - CollectionMenu(QWidget *parent = 0); - QSize sizeHint() const override; -}; - -CollectionMenu::CollectionMenu(QWidget *parent) - : QMenu(parent) -{ -} -QSize CollectionMenu::sizeHint() const -{ - return layout()->sizeHint(); -} - -// -// ShapeCollectionDockerFactory -// - -ShapeCollectionDockerFactory::ShapeCollectionDockerFactory() - : KoDockFactoryBase() -{ -} - -QString ShapeCollectionDockerFactory::id() const -{ - return QString("ShapeCollectionDocker"); -} - -QDockWidget *ShapeCollectionDockerFactory::createDockWidget() -{ - ShapeCollectionDocker *docker = new ShapeCollectionDocker(); - - return docker; -} - -void ShapeCollectionDocker::locationChanged(Qt::DockWidgetArea area) -{ - resize(0, 0); - - switch (area) { - case Qt::TopDockWidgetArea: - case Qt::BottomDockWidgetArea: - m_spacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); - break; - case Qt::LeftDockWidgetArea: - case Qt::RightDockWidgetArea: - m_spacer->changeSize(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - break; - default: - break; - } - m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); - m_layout->invalidate(); -} - -// -// ShapeCollectionDocker -// - -ShapeCollectionDocker::ShapeCollectionDocker(QWidget *parent) - : QDockWidget(parent) -{ - setWindowTitle(i18n("Add Shape")); - - QWidget *mainWidget = new QWidget(this); - m_layout = new QGridLayout(mainWidget); - m_layout->setMargin(0); - m_layout->setHorizontalSpacing(0); - m_layout->setVerticalSpacing(0); - m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); - setWidget(mainWidget); - - m_quickView = new QListView(mainWidget); - m_layout->addWidget(m_quickView, 0, 0); - m_quickView->setViewMode(QListView::IconMode); - m_quickView->setDragDropMode(QListView::DragOnly); - m_quickView->setSelectionMode(QListView::SingleSelection); - m_quickView->setResizeMode(QListView::Adjust); - m_quickView->setFlow(QListView::LeftToRight); - m_quickView->setGridSize(QSize(40, 44)); - m_quickView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_quickView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_quickView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - m_quickView->setTextElideMode(Qt::ElideNone); - m_quickView->setWordWrap(true); - - m_spacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); - m_layout->addItem(m_spacer, 1, 2); - m_layout->setRowStretch(1, 1); - m_layout->setColumnStretch(2, 1); - - connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(locationChanged(Qt::DockWidgetArea))); - - connect(m_quickView, SIGNAL(clicked(QModelIndex)), - this, SLOT(activateShapeCreationToolFromQuick(QModelIndex))); - - m_moreShapes = new QToolButton(mainWidget); - m_moreShapes->setText(i18n("More")); - m_moreShapes->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_moreShapes->setIconSize(QSize(32, 32)); - m_moreShapes->setIcon(koIcon("shape-choose")); - m_moreShapes->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - m_layout->addWidget(m_moreShapes, 0, 1); - - m_moreShapesContainer = new CollectionMenu(mainWidget); - m_moreShapes->setMenu(m_moreShapesContainer); - m_moreShapes->setPopupMode(QToolButton::InstantPopup); - QGridLayout *containerLayout = new QGridLayout(m_moreShapesContainer); - containerLayout->setMargin(4); - - m_collectionChooser = new QListWidget(m_moreShapesContainer); - containerLayout->addWidget(m_collectionChooser, 0, 0, 1, 2); - m_collectionChooser->setViewMode(QListView::IconMode); - m_collectionChooser->setSelectionMode(QListView::SingleSelection); - m_collectionChooser->setResizeMode(QListView::Adjust); - m_collectionChooser->setGridSize(QSize(75, 64)); - m_collectionChooser->setMovement(QListView::Static); - m_collectionChooser->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_collectionChooser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - connect(m_collectionChooser, SIGNAL(itemClicked(QListWidgetItem*)), - this, SLOT(activateShapeCollection(QListWidgetItem*))); - - m_addCollectionButton = new QToolButton(m_moreShapesContainer); - containerLayout->addWidget(m_addCollectionButton, 1, 0); - m_addCollectionButton->setIcon(koIcon("list-add")); - m_addCollectionButton->setIconSize(QSize(16, 16)); - m_addCollectionButton->setToolTip(i18n("Open Shape Collection")); - m_addCollectionButton->setPopupMode(QToolButton::InstantPopup); - m_addCollectionButton->setVisible(false); - - m_closeCollectionButton = new QToolButton(m_moreShapesContainer); - containerLayout->addWidget(m_closeCollectionButton, 1, 1); - m_closeCollectionButton->setIcon(koIcon("list-remove")); - m_closeCollectionButton->setIconSize(QSize(16, 16)); - m_closeCollectionButton->setToolTip(i18n("Remove Shape Collection")); - m_closeCollectionButton->setVisible(false); - - connect(m_closeCollectionButton, SIGNAL(clicked()), - this, SLOT(removeCurrentCollection())); - - if (!KoResourcePaths::resourceDirs("app_shape_collections").isEmpty()) { - buildAddCollectionMenu(); - } - - m_collectionView = new QListView(m_moreShapesContainer); - containerLayout->addWidget(m_collectionView, 0, 2, -1, 1); - m_collectionView->setViewMode(QListView::IconMode); - m_collectionView->setDragDropMode(QListView::DragOnly); - m_collectionView->setSelectionMode(QListView::SingleSelection); - m_collectionView->setResizeMode(QListView::Adjust); - m_collectionView->setGridSize(QSize(48 + 20, 48)); - m_collectionView->setFixedSize(QSize(165, 345)); - m_collectionView->setWordWrap(true); - - connect(m_collectionView, SIGNAL(clicked(QModelIndex)), - this, SLOT(activateShapeCreationTool(QModelIndex))); - - // Load the default shapes and add them to the combobox - loadDefaultShapes(); -} - -void ShapeCollectionDocker::setCanvas(KoCanvasBase *canvas) -{ - setEnabled(canvas != 0); -} - -void ShapeCollectionDocker::unsetCanvas() -{ - setEnabled(false); -} - -void ShapeCollectionDocker::loadDefaultShapes() -{ - QList defaultList; - QList arrowList; - QList funnyList; - QList geometricList; - QList quicklist; - int quickCount = 0; - - QStringList quickShapes; - quickShapes << "TextShapeID" << "PictureShape" << "ChartShape" << "ArtisticText"; - KConfigGroup cfg = KSharedConfig::openConfig()->group("KoShapeCollection"); - quickShapes = cfg.readEntry("QuickShapes", quickShapes); - - Q_FOREACH (const QString &id, KoShapeRegistry::instance()->keys()) { - KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); - // don't show hidden factories - if (factory->hidden()) { - continue; - } - bool oneAdded = false; - - Q_FOREACH (const KoShapeTemplate &shapeTemplate, factory->templates()) { - oneAdded = true; - KoCollectionItem temp; - temp.id = shapeTemplate.id; - temp.name = shapeTemplate.name; - temp.toolTip = shapeTemplate.toolTip; - temp.icon = KisIconUtils::loadIcon(shapeTemplate.iconName); - temp.properties = shapeTemplate.properties; - if (shapeTemplate.family == "funny") { - funnyList.append(temp); - } else if (shapeTemplate.family == "arrow") { - arrowList.append(temp); - } else if (shapeTemplate.family == "geometric") { - geometricList.append(temp); - } else { - defaultList.append(temp); - } - - QString id = temp.id; - if (!shapeTemplate.templateId.isEmpty()) { - id += '_' + shapeTemplate.templateId; - } - - if (quickShapes.contains(id)) { - quicklist.append(temp); - quickCount++; - } - } - - if (!oneAdded) { - KoCollectionItem temp; - temp.id = factory->id(); - temp.name = factory->name(); - temp.toolTip = factory->toolTip(); - temp.icon = KisIconUtils::loadIcon(factory->iconName()); - temp.properties = 0; - if (factory->family() == "funny") { - funnyList.append(temp); - } else if (factory->family() == "arrow") { - arrowList.append(temp); - } else if (factory->family() == "geometric") { - geometricList.append(temp); - } else { - defaultList.append(temp); - } - - if (quickShapes.contains(temp.id)) { - quicklist.append(temp); - quickCount++; - } - } - } - - CollectionItemModel *model = new CollectionItemModel(this); - model->setShapeTemplateList(defaultList); - addCollection("default", i18n("Default"), model); - - model = new CollectionItemModel(this); - model->setShapeTemplateList(geometricList); - addCollection("geometric", i18n("Geometrics"), model); - - model = new CollectionItemModel(this); - model->setShapeTemplateList(arrowList); - addCollection("arrow", i18n("Arrows"), model); - - model = new CollectionItemModel(this); - model->setShapeTemplateList(funnyList); - addCollection("funny", i18n("Funny"), model); - - CollectionItemModel *quickModel = new CollectionItemModel(this); - quickModel->setShapeTemplateList(quicklist); - m_quickView->setModel(quickModel); - - int fw = m_quickView->frameWidth(); - m_quickView->setMaximumSize(QSize(quickCount * 40 + 2 * fw + 1, 44 + 2 * fw + 1)); - m_quickView->setMinimumSize(QSize(quickCount * 40 + 2 * fw + 1, 44 + 2 * fw + 1)); - - m_collectionChooser->setMinimumSize(QSize(75 + 2 * fw, 0)); - m_collectionChooser->setMaximumSize(QSize(75 + 2 * fw, 1000)); - - m_collectionChooser->setCurrentRow(0); - activateShapeCollection(m_collectionChooser->item(0)); -} - -void ShapeCollectionDocker::activateShapeCreationToolFromQuick(const QModelIndex &index) -{ - m_collectionView->setFont(m_quickView->font()); - if (!index.isValid()) { - return; - } - - KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController(); - - if (canvasController) { - KoCreateShapesTool *tool = KoToolManager::instance()->shapeCreatorTool(canvasController->canvas()); - QString id = m_quickView->model()->data(index, Qt::UserRole).toString(); - const KoProperties *properties = static_cast(m_quickView->model())->properties(index); - - tool->setShapeId(id); - tool->setShapeProperties(properties); - KoToolManager::instance()->switchToolRequested(KoCreateShapesTool_ID); - } - m_quickView->clearSelection(); -} - -void ShapeCollectionDocker::activateShapeCreationTool(const QModelIndex &index) -{ - if (!index.isValid()) { - return; - } - - KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController(); - - if (canvasController) { - KoCreateShapesTool *tool = KoToolManager::instance()->shapeCreatorTool(canvasController->canvas()); - QString id = m_collectionView->model()->data(index, Qt::UserRole).toString(); - const KoProperties *properties = static_cast(m_collectionView->model())->properties(index); - - tool->setShapeId(id); - tool->setShapeProperties(properties); - KoToolManager::instance()->switchToolRequested(KoCreateShapesTool_ID); - } - m_moreShapesContainer->hide(); -} - -void ShapeCollectionDocker::activateShapeCollection(QListWidgetItem *item) -{ - QString id = item->data(Qt::UserRole).toString(); - - if (m_modelMap.contains(id)) { - m_collectionView->setModel(m_modelMap[id]); - } else { - qCritical() << "Didn't find a model with id ==" << id; - } - - m_closeCollectionButton->setEnabled(id != "default"); -} - -bool ShapeCollectionDocker::addCollection(const QString &id, const QString &title, - CollectionItemModel *model) -{ - if (m_modelMap.contains(id)) { - return false; - } - - m_modelMap.insert(id, model); - QListWidgetItem *collectionChooserItem = new QListWidgetItem(koIcon("shape-choose"), title); - collectionChooserItem->setData(Qt::UserRole, id); - m_collectionChooser->addItem(collectionChooserItem); - return true; -} - -void ShapeCollectionDocker::buildAddCollectionMenu() -{ - QStringList dirs = KoResourcePaths::resourceDirs("app_shape_collections"); - QMenu *menu = new QMenu(m_addCollectionButton); - m_addCollectionButton->setMenu(menu); - - Q_FOREACH (const QString &dirName, dirs) { - QDir dir(dirName); - - if (!dir.exists()) { - continue; - } - - QStringList collectionDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - - Q_FOREACH (const QString &collectionDirName, collectionDirs) { - scanCollectionDir(dirName + collectionDirName, menu); - } - } -} - -void ShapeCollectionDocker::scanCollectionDir(const QString &path, QMenu *menu) -{ - QDir dir(path); - - if (!dir.exists(".directory")) { - return; - } - - KDesktopFile directory(dir.absoluteFilePath(".directory")); - KConfigGroup dg = directory.desktopGroup(); - QString name = dg.readEntry("Name"); - QString icon = dg.readEntry("Icon"); - QString type = dg.readEntry("X-KDE-DirType"); - - if (type == "subdir") { - QMenu *submenu = menu->addMenu(QIcon(dir.absoluteFilePath(icon)), name); - QStringList collectionDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - - Q_FOREACH (const QString &collectionDirName, collectionDirs) { - scanCollectionDir(dir.absoluteFilePath(collectionDirName), submenu); - } - } else { - QAction *action = menu->addAction(QIcon(dir.absoluteFilePath(icon)), name, this, SLOT(loadCollection())); - action->setIconText(name); - action->setData(QVariant(type + ':' + path + QDir::separator())); - action->setEnabled(!m_modelMap.contains(action->data().toString())); - } -} - -void ShapeCollectionDocker::loadCollection() -{ - QAction *action = qobject_cast(sender()); - - if (!action) { - return; - } - - QString path = action->data().toString(); - int index = path.indexOf(':'); - QString type = path.left(index); - path = path.mid(index + 1); - - if (m_modelMap.contains(path)) { - return; - } - - CollectionItemModel *model = new CollectionItemModel(this); - addCollection(path, action->iconText(), model); - action->setEnabled(false); -} - - -QIcon ShapeCollectionDocker::generateShapeIcon(KoShape *shape) -{ - KoZoomHandler converter; - - qreal diffx = 30 / converter.documentToViewX(shape->size().width()); - qreal diffy = 30 / converter.documentToViewY(shape->size().height()); - converter.setZoom(qMin(diffx, diffy)); - - QPixmap pixmap(qRound(converter.documentToViewX(shape->size().width())) + 2, qRound(converter.documentToViewY(shape->size().height())) + 2); - pixmap.fill(Qt::white); - QPainter painter(&pixmap); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.translate(1, 1); - KoShapePaintingContext paintContext; //FIXME - shape->paint(painter, converter, paintContext); - painter.end(); - - return QIcon(pixmap); -} - -void ShapeCollectionDocker::removeCollection(const QString &id) -{ -//TODO m_collectionsCombo->removeItem(m_collectionsCombo->findData(id)); - - if (m_modelMap.contains(id)) { - CollectionItemModel *model = m_modelMap[id]; - QList list = model->shapeTemplateList(); - Q_FOREACH (const KoCollectionItem &temp, list) { - KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(temp.id); - KoShapeRegistry::instance()->remove(temp.id); - delete factory; - } - - m_modelMap.remove(id); - delete model; - } -} - -void ShapeCollectionDocker::removeCurrentCollection() -{ -//TODO removeCollection(m_collectionsCombo->itemData(m_collectionsCombo->currentIndex()).toString()); -} diff --git a/plugins/extensions/CMakeLists.txt b/plugins/extensions/CMakeLists.txt index 963ba32290..cdf8475a9f 100644 --- a/plugins/extensions/CMakeLists.txt +++ b/plugins/extensions/CMakeLists.txt @@ -1,28 +1,29 @@ add_subdirectory( bigbrother ) add_subdirectory( imagesplit ) add_subdirectory( clonesarray ) add_subdirectory( colorrange ) add_subdirectory( colorspaceconversion ) add_subdirectory( histogram ) add_subdirectory( imagesize ) add_subdirectory( metadataeditor ) add_subdirectory( modify_selection ) add_subdirectory( offsetimage ) add_subdirectory( rotateimage ) add_subdirectory( separate_channels ) add_subdirectory( shearimage ) add_subdirectory( layergroupswitcher ) add_subdirectory( resourcemanager ) add_subdirectory( layersplit ) add_subdirectory( animationrenderer ) add_subdirectory( waveletdecompose ) # Allow to skip building GMIC plugin option(WITH_GMIC "Build the G'Mic plugin" ON) if(WITH_GMIC) if (CMAKE_COMPILER_IS_GNUCC) add_subdirectory( gmic ) endif() endif() -#add_subdirectory( pykrita ) +add_subdirectory( pykrita ) +add_subdirectory( buginfo ) diff --git a/plugins/extensions/buginfo/CMakeLists.txt b/plugins/extensions/buginfo/CMakeLists.txt new file mode 100644 index 0000000000..a102ed6838 --- /dev/null +++ b/plugins/extensions/buginfo/CMakeLists.txt @@ -0,0 +1,10 @@ +set(kritabuginfo_SOURCES + buginfo.cpp + dlg_buginfo.cpp +) + +ki18n_wrap_ui(kritabuginfo_SOURCES wdg_buginfo.ui ) +add_library(kritabuginfo MODULE ${kritabuginfo_SOURCES}) +target_link_libraries(kritabuginfo kritaui) +install(TARGETS kritabuginfo DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) +install( FILES buginfo.xmlgui DESTINATION ${DATA_INSTALL_DIR}/kritaplugins) diff --git a/plugins/extensions/buginfo/buginfo.cpp b/plugins/extensions/buginfo/buginfo.cpp new file mode 100644 index 0000000000..a861f03049 --- /dev/null +++ b/plugins/extensions/buginfo/buginfo.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017 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 "buginfo.h" + +#include + +#include +#include +#include +#include +#include +#include +#include "dlg_buginfo.h" + +K_PLUGIN_FACTORY_WITH_JSON(BugInfoFactory, "kritabuginfo.json", registerPlugin();) + +BugInfo::BugInfo(QObject *parent, const QVariantList &) + : KisViewPlugin(parent) +{ + KisAction *action = createAction("buginfo"); + action->setText(i18n("Show system information for bug reports.")); + connect(action, SIGNAL(triggered()), this, SLOT(slotBugInfo())); +} + + +BugInfo::~BugInfo() +{ +} + +void BugInfo::slotBugInfo() +{ + DlgBugInfo dlgBugInfo(m_view->mainWindow()); + dlgBugInfo.exec(); +} + +#include "buginfo.moc" diff --git a/libs/flake/KoSvgPaste.h b/plugins/extensions/buginfo/buginfo.h similarity index 64% copy from libs/flake/KoSvgPaste.h copy to plugins/extensions/buginfo/buginfo.h index 98400cf6e1..e6d031441c 100644 --- a/libs/flake/KoSvgPaste.h +++ b/plugins/extensions/buginfo/buginfo.h @@ -1,38 +1,40 @@ /* - * Copyright (c) 2017 Dmitry Kazakov + * Copyright (c) 2017 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 KOSVGPASTE_H -#define KOSVGPASTE_H +#ifndef BUGINFO_H +#define BUGINFO_H -#include "kritaflake_export.h" -#include +#include +#include -class KoShape; -class QRectF; -class QSizeF; +class KUndo2MagicString; -class KRITAFLAKE_EXPORT KoSvgPaste +class BugInfo : public KisViewPlugin { + Q_OBJECT public: - KoSvgPaste(); + BugInfo(QObject *parent, const QVariantList &); + ~BugInfo() override; + +public Q_SLOTS: + + void slotBugInfo(); - bool hasShapes() const; - QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const; }; -#endif // KOSVGPASTE_H +#endif // BUGINFO_H diff --git a/plugins/extensions/buginfo/buginfo.xmlgui b/plugins/extensions/buginfo/buginfo.xmlgui new file mode 100644 index 0000000000..1002c9d910 --- /dev/null +++ b/plugins/extensions/buginfo/buginfo.xmlgui @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/extensions/buginfo/dlg_buginfo.cpp b/plugins/extensions/buginfo/dlg_buginfo.cpp new file mode 100644 index 0000000000..d1654f6c0b --- /dev/null +++ b/plugins/extensions/buginfo/dlg_buginfo.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2017 Boudewijn Rempt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "dlg_buginfo.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kis_document_aware_spin_box_unit_manager.h" + +DlgBugInfo::DlgBugInfo(QWidget *parent) + : KoDialog(parent) +{ + setCaption(i18n("Please paste this information in your bug report")); + + setButtons(User1 | Ok); + setButtonText(User1, i18n("Copy to clipboard")); + setDefaultButton(Ok); + + m_page = new WdgBugInfo(this); + Q_CHECK_PTR(m_page); + + setMainWidget(m_page); + + // OS information + QString info; + info.append("OS Information"); + info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); + info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); + info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); + info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); + info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); + info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); + info.append("\n Product Type: ").append(QSysInfo::productType()); + info.append("\n Product Version: ").append(QSysInfo::productVersion()); + info.append("\n"); + + // OpenGL information + // we need a QSurface active to get our GL functions from the context + QWindow surface; + surface.setSurfaceType( QSurface::OpenGLSurface ); + surface.create(); + + QOpenGLContext context; + context.create(); + if (!context.isValid()) return; + + context.makeCurrent( &surface ); + + QOpenGLFunctions *funcs = context.functions(); + funcs->initializeOpenGLFunctions(); + +#ifndef GL_RENDERER +# define GL_RENDERER 0x1F01 +#endif + QString Renderer = QString((const char*)funcs->glGetString(GL_RENDERER)); + info.append("\nOpenGL Info"); + info.append("\n Vendor: ").append(reinterpret_cast(funcs->glGetString(GL_VENDOR))); + info.append("\n Renderer: ").append(Renderer); + info.append("\n Version: ").append(reinterpret_cast(funcs->glGetString(GL_VERSION))); + info.append("\n Shading language: ").append(reinterpret_cast(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION))); + + int glMajorVersion = context.format().majorVersion(); + int glMinorVersion = context.format().minorVersion(); + bool supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); + + info.append(QString("\n Version: %1.%2").arg(glMajorVersion).arg(glMinorVersion)); + info.append(QString("\n Supports deprecated functions: %1").arg(supportsDeprecatedFunctions ? "true" : "false")); + + + + // Installation information + + // calculate a default height for the widget + int wheight = m_page->sizeHint().height(); + m_page->txtBugInfo->setText(info); + + QFontMetrics fm = m_page->txtBugInfo->fontMetrics(); + int target_height = fm.height() * info.split('\n').size() + wheight; + + QDesktopWidget dw; + QRect screen_rect = dw.availableGeometry(dw.primaryScreen()); + + resize(sizeHint().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height); + + connect(this, &KoDialog::user1Clicked, this, [this](){ QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText()); }); +} + +DlgBugInfo::~DlgBugInfo() +{ + delete m_page; +} diff --git a/libs/flake/KoSvgPaste.h b/plugins/extensions/buginfo/dlg_buginfo.h similarity index 61% copy from libs/flake/KoSvgPaste.h copy to plugins/extensions/buginfo/dlg_buginfo.h index 98400cf6e1..619a737b05 100644 --- a/libs/flake/KoSvgPaste.h +++ b/plugins/extensions/buginfo/dlg_buginfo.h @@ -1,38 +1,47 @@ /* - * Copyright (c) 2017 Dmitry Kazakov + * Copyright (c) 2017 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 KOSVGPASTE_H -#define KOSVGPASTE_H +#ifndef DLG_BUGINFO +#define DLG_BUGINFO -#include "kritaflake_export.h" -#include +#include -class KoShape; -class QRectF; -class QSizeF; +#include "ui_wdg_buginfo.h" -class KRITAFLAKE_EXPORT KoSvgPaste + +class WdgBugInfo : public QWidget, public Ui::WdgBugInfo { + Q_OBJECT + public: - KoSvgPaste(); + WdgBugInfo(QWidget *parent) : QWidget(parent) { + setupUi(this); + } +}; - bool hasShapes() const; - QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const; +class DlgBugInfo: public KoDialog +{ + Q_OBJECT +public: + DlgBugInfo(QWidget * parent = 0); + ~DlgBugInfo() override; +private: + WdgBugInfo *m_page; }; -#endif // KOSVGPASTE_H +#endif // DLG_BUGINFO diff --git a/plugins/extensions/buginfo/kritabuginfo.json b/plugins/extensions/buginfo/kritabuginfo.json new file mode 100644 index 0000000000..c8deddf33d --- /dev/null +++ b/plugins/extensions/buginfo/kritabuginfo.json @@ -0,0 +1,9 @@ +{ + "Id": "Buginfo Plugin", + "Type": "Service", + "X-KDE-Library": "kritabuginfo", + "X-KDE-ServiceTypes": [ + "Krita/ViewPlugin" + ], + "X-Krita-Version": "28" +} diff --git a/plugins/extensions/buginfo/wdg_buginfo.ui b/plugins/extensions/buginfo/wdg_buginfo.ui new file mode 100644 index 0000000000..35c684948a --- /dev/null +++ b/plugins/extensions/buginfo/wdg_buginfo.ui @@ -0,0 +1,31 @@ + + + WdgBugInfo + + + + 0 + 0 + 367 + 439 + + + + Rotate Image + + + + + + Please add this information to a bug report: + + + + + + + + + + + diff --git a/plugins/extensions/imagesize/dlg_imagesize.cc b/plugins/extensions/imagesize/dlg_imagesize.cc index 846c7fa66b..65d48eaf16 100644 --- a/plugins/extensions/imagesize/dlg_imagesize.cc +++ b/plugins/extensions/imagesize/dlg_imagesize.cc @@ -1,422 +1,421 @@ /* * dlg_imagesize.cc - part of KimageShop^WKrayon^WKrita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2009 C. Boemann * Copyright (c) 2013 Juan Palacios * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_imagesize.h" #include #include #include #include - #include #include "kis_double_parse_unit_spin_box.h" #include "kis_document_aware_spin_box_unit_manager.h" static const QString pixelStr(KoUnit::unitDescription(KoUnit::Pixel)); static const QString percentStr(i18n("Percent (%)")); static const QString pixelsInchStr(i18n("Pixels/Inch")); static const QString pixelsCentimeterStr(i18n("Pixels/Centimeter")); DlgImageSize::DlgImageSize(QWidget *parent, int width, int height, double resolution) : KoDialog(parent) , m_aspectRatio(((double) width) / height) , m_originalWidth(width) , m_originalHeight(height) , m_width(width) , m_height(height) , m_printWidth(width / resolution) , m_printHeight(height / resolution) , m_originalResolution(resolution) , m_resolution(resolution) , m_keepAspect(true) { setCaption(i18n("Scale To New Size")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgImageSize(this); Q_CHECK_PTR(m_page); m_page->layout()->setMargin(0); m_page->setObjectName("image_size"); _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); //configure the unit to image length, default unit is pixel and printing units are forbiden. _widthUnitManager->setUnitDimension(KisSpinBoxUnitManager::IMLENGTH); _heightUnitManager->setUnitDimension(KisSpinBoxUnitManager::IMLENGTH); m_page->pixelWidthDouble->setUnitManager(_widthUnitManager); m_page->pixelHeightDouble->setUnitManager(_heightUnitManager); m_page->pixelWidthDouble->changeValue(width); m_page->pixelHeightDouble->changeValue(height); m_page->pixelWidthDouble->setDecimals(2); m_page->pixelHeightDouble->setDecimals(2); m_page->pixelWidthDouble->setDisplayUnit(false); m_page->pixelHeightDouble->setDisplayUnit(false); m_page->pixelWidthUnit->setModel(_widthUnitManager); m_page->pixelHeightUnit->setModel(_widthUnitManager); m_page->pixelWidthUnit->setCurrentText("px"); m_page->pixelHeightUnit->setCurrentText("px"); m_page->pixelFilterCmb->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); - m_page->pixelFilterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formatedDescriptions()); + m_page->pixelFilterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formattedDescriptions()); m_page->pixelFilterCmb->setCurrent("Bicubic"); _printWidthUnitManager = new KisSpinBoxUnitManager(this); _printHeightUnitManager = new KisSpinBoxUnitManager(this); m_page->printWidth->setUnitManager(_printWidthUnitManager); m_page->printHeight->setUnitManager(_printHeightUnitManager); m_page->printWidth->setDecimals(2); m_page->printHeight->setDecimals(2); m_page->printWidth->setDisplayUnit(false); m_page->printHeight->setDisplayUnit(false); m_page->printResolution->setDecimals(2); m_page->printResolution->setAlignment(Qt::AlignRight); m_page->printWidthUnit->setModel(_printWidthUnitManager); m_page->printHeightUnit->setModel(_printHeightUnitManager); m_page->printWidth->changeValue(m_printWidth); m_page->printHeight->changeValue(m_printHeight); //TODO: create a resolution dimension in the unit manager. m_page->printResolutionUnit->addItem(pixelsInchStr); m_page->printResolutionUnit->addItem(pixelsCentimeterStr); m_page->pixelAspectRatioBtn->setKeepAspectRatio(true); m_page->printAspectRatioBtn->setKeepAspectRatio(true); m_page->constrainProportionsCkb->setChecked(true); KisSizeGroup *labelsGroup = new KisSizeGroup(this); labelsGroup->addWidget(m_page->lblPixelWidth); labelsGroup->addWidget(m_page->lblPixelHeight); labelsGroup->addWidget(m_page->lblPixelFilter); labelsGroup->addWidget(m_page->lblPrintWidth); labelsGroup->addWidget(m_page->lblPrintHeight); labelsGroup->addWidget(m_page->lblResolution); KisSizeGroup *spinboxesGroup = new KisSizeGroup(this); spinboxesGroup->addWidget(m_page->pixelWidthDouble); spinboxesGroup->addWidget(m_page->pixelHeightDouble); spinboxesGroup->addWidget(m_page->printWidth); spinboxesGroup->addWidget(m_page->printHeight); spinboxesGroup->addWidget(m_page->printResolution); KisSizeGroup *comboboxesGroup = new KisSizeGroup(this); comboboxesGroup->addWidget(m_page->pixelWidthUnit); comboboxesGroup->addWidget(m_page->pixelHeightUnit); comboboxesGroup->addWidget(m_page->printWidthUnit); comboboxesGroup->addWidget(m_page->printHeightUnit); comboboxesGroup->addWidget(m_page->printResolutionUnit); connect(this, SIGNAL(okClicked()), this, SLOT(accept())); connect(m_page->pixelAspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->printAspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->pixelWidthDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotPixelWidthChanged(double))); connect(m_page->pixelHeightDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotPixelHeightChanged(double))); connect(m_page->pixelWidthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->pixelHeightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->pixelWidthUnit, SLOT(setCurrentIndex(int))); connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->pixelHeightUnit, SLOT(setCurrentIndex(int))); connect(m_page->printWidth, SIGNAL(valueChangedPt(double)), this, SLOT(slotPrintWidthChanged(double))); connect(m_page->printHeight, SIGNAL(valueChangedPt(double)), this, SLOT(slotPrintHeightChanged(double))); connect(m_page->printWidthUnit, SIGNAL(currentIndexChanged(int)), _printWidthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->printHeightUnit, SIGNAL(currentIndexChanged(int)), _printHeightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_printWidthUnitManager, SIGNAL(unitChanged(int)), m_page->printWidthUnit, SLOT(setCurrentIndex(int))); connect(_printHeightUnitManager, SIGNAL(unitChanged(int)), m_page->printHeightUnit, SLOT(setCurrentIndex(int))); connect(m_page->printResolution, SIGNAL(valueChanged(double)), this, SLOT(slotPrintResolutionChanged(double))); connect(m_page->printResolution, SIGNAL(editingFinished()), this, SLOT(slotPrintResolutionEditFinished())); connect(m_page->printResolutionUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPrintResolutionUnitChanged())); // pick selected print units from user locale (after slots connection, so the spinbox will be updated too). if (QLocale().measurementSystem() == QLocale::MetricSystem) { m_page->printWidthUnit->setCurrentText("cm"); m_page->printHeightUnit->setCurrentText("cm"); m_page->printResolutionUnit->setCurrentIndex(0); // Pixels/Centimeter slotPrintResolutionUnitChanged(); //ensure the resolution is updated, even if the index didn't changed. } else { // Imperial m_page->printWidthUnit->setCurrentText("in"); m_page->printHeightUnit->setCurrentText("in"); m_page->printResolutionUnit->setCurrentIndex(1); // Pixels/Inch slotPrintResolutionUnitChanged(); //ensure the resolution is updated, even if the index didn't changed. } setMainWidget(m_page); } DlgImageSize::~DlgImageSize() { delete m_page; } qint32 DlgImageSize::width() { return (qint32)m_width; } qint32 DlgImageSize::height() { return (qint32)m_height; } double DlgImageSize::resolution() { return m_resolution; } KisFilterStrategy *DlgImageSize::filterType() { KoID filterID = m_page->pixelFilterCmb->currentItem(); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value(filterID.id()); return filter; } // SLOTS void DlgImageSize::slotPixelWidthChanged(double w) { m_width = w; m_printWidth = m_width / m_resolution; updatePrintWidthUIValue(m_printWidth); if (m_keepAspect) { m_height = qRound(m_width / m_aspectRatio); updatePixelHeightUIValue(m_height); m_printHeight = m_height / m_resolution; updatePrintHeightUIValue(m_printHeight); } } void DlgImageSize::slotPixelHeightChanged(double h) { m_height = h; m_printHeight = m_height / m_resolution; updatePrintHeightUIValue(m_printHeight); if (m_keepAspect) { m_width = qRound(m_height * m_aspectRatio); updatePixelWidthUIValue(m_width); m_printWidth = m_width / m_resolution; updatePrintWidthUIValue(m_printWidth); } } void DlgImageSize::slotPrintWidthChanged(double w) { m_printWidth = w; if (m_keepAspect) { m_printHeight = m_printWidth / m_aspectRatio; updatePrintHeightUIValue(m_printHeight); } if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) { m_resolution = m_width / m_printWidth; updatePrintResolutionUIValue(m_resolution); if (!m_keepAspect) { // compute and update a new image height value from the print size values const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); m_height = qRound(printHeightInch * 72 * m_resolution); updatePixelHeightUIValue(m_height); } } else { const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); m_width = qRound(printWidthInch * 72 * m_resolution); updatePixelWidthUIValue(m_width); if (m_keepAspect) { m_height = qRound(m_width / m_aspectRatio); updatePixelHeightUIValue(m_height); } } } void DlgImageSize::slotPrintHeightChanged(double h) { m_printHeight = h; if (m_keepAspect) { m_printWidth = m_printHeight * m_aspectRatio; updatePrintWidthUIValue(m_printWidth); } if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) { m_resolution = m_height / m_printHeight; updatePrintResolutionUIValue(m_resolution); if (!m_keepAspect) { // compute and update a new image width value from the print size values const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); m_width = qRound(printWidthInch * 72 * m_resolution); updatePixelWidthUIValue(m_width); } } else { const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); m_height = qRound(printHeightInch * 72 * m_resolution); updatePixelHeightUIValue(m_height); if (m_keepAspect) { m_width = qRound(m_height * m_aspectRatio); updatePixelWidthUIValue(m_width); } } } void DlgImageSize::slotAspectChanged(bool keep) { m_page->pixelAspectRatioBtn->blockSignals(true); m_page->printAspectRatioBtn->blockSignals(true); m_page->constrainProportionsCkb->blockSignals(true); m_page->pixelAspectRatioBtn->setKeepAspectRatio(keep); m_page->printAspectRatioBtn->setKeepAspectRatio(keep); m_page->constrainProportionsCkb->setChecked(keep); m_page->pixelAspectRatioBtn->blockSignals(false); m_page->printAspectRatioBtn->blockSignals(false); m_page->constrainProportionsCkb->blockSignals(false); m_keepAspect = keep; if (keep) { // values may be out of sync, so we need to reset it to defaults m_width = m_originalWidth; m_height = m_originalHeight; m_printWidth = m_originalWidth / m_originalResolution; m_printHeight = m_originalHeight / m_originalResolution; m_resolution = m_originalResolution; updatePixelWidthUIValue(m_width); updatePixelHeightUIValue(m_height); updatePrintWidthUIValue(m_printWidth); updatePrintHeightUIValue(m_printHeight); updatePrintResolutionUIValue(m_resolution); } } void DlgImageSize::slotPrintResolutionChanged(double r) { if (m_page->printResolutionUnit->currentText() == pixelsInchStr) m_resolution = KoUnit::convertFromUnitToUnit(r, KoUnit(KoUnit::Pixel), KoUnit(KoUnit::Inch)); else m_resolution = KoUnit::convertFromUnitToUnit(r, KoUnit(KoUnit::Pixel), KoUnit(KoUnit::Centimeter)); if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) { m_printWidth = m_width / m_resolution; m_printHeight = m_height / m_resolution; updatePrintWidthUIValue(m_printWidth); updatePrintHeightUIValue(m_printHeight); } else { // Do not commit m_width and m_height values yet. This is done to avoid // nasty results in image size values while the user is typing a resolution value const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); const int width = qRound(printWidthInch * 72 * m_resolution); const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); const int height = qRound(printHeightInch * 72 * m_resolution); updatePixelWidthUIValue(width); updatePixelHeightUIValue(height); } } void DlgImageSize::slotPrintResolutionEditFinished() { if (!m_page->adjustPrintSizeSeparatelyCkb->isChecked()) { const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); // Commit width and height values m_width = qRound(printWidthInch * 72 * m_resolution); m_height = qRound(printHeightInch * 72 * m_resolution); // Note that spinbox values should be up to date // (updated through slotResolutionChanged()) } } void DlgImageSize::slotPrintResolutionUnitChanged() { updatePrintResolutionUIValue(m_resolution); } void DlgImageSize::updatePixelWidthUIValue(double value) { m_page->pixelWidthDouble->blockSignals(true); m_page->pixelWidthDouble->changeValue(value); m_page->pixelWidthDouble->blockSignals(false); } void DlgImageSize::updatePixelHeightUIValue(double value) { m_page->pixelHeightDouble->blockSignals(true); m_page->pixelHeightDouble->changeValue(value); m_page->pixelHeightDouble->blockSignals(false); } void DlgImageSize::updatePrintWidthUIValue(double value) { m_page->printWidth->blockSignals(true); m_page->printWidth->changeValue(value); m_page->printWidth->blockSignals(false); } void DlgImageSize::updatePrintHeightUIValue(double value) { m_page->printHeight->blockSignals(true); m_page->printHeight->changeValue(value); m_page->printHeight->blockSignals(false); } void DlgImageSize::updatePrintResolutionUIValue(double value) { double uiValue = 0.0; if (m_page->printResolutionUnit->currentText() == pixelsInchStr) { // show the value in pixel/inch unit uiValue = KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel)); } else { // show the value in pixel/centimeter unit uiValue = KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Centimeter), KoUnit(KoUnit::Pixel)); } m_page->printResolution->blockSignals(true); m_page->printResolution->setValue(uiValue); m_page->printResolution->blockSignals(false); } diff --git a/plugins/extensions/imagesize/dlg_layersize.cc b/plugins/extensions/imagesize/dlg_layersize.cc index 621eaf24e3..290b9f829f 100644 --- a/plugins/extensions/imagesize/dlg_layersize.cc +++ b/plugins/extensions/imagesize/dlg_layersize.cc @@ -1,197 +1,197 @@ /* * dlg_layersize.cc - part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Sven Langkamp * Copyright (c) 2013 Juan Palacios * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_layersize.h" #include #include #include #include // XXX: I'm really real bad at arithmetic, let alone math. Here // be rounding errors. (Boudewijn) static const QString pixelStr(KoUnit::unitDescription(KoUnit::Pixel)); static const QString percentStr(i18n("Percent (%)")); DlgLayerSize::DlgLayerSize(QWidget * parent, const char * name, int width, int height, double resolution) : KoDialog(parent) , m_aspectRatio(((double) width) / height) , m_originalWidth(width) , m_originalHeight(height) , m_width(width) , m_height(height) , m_resolution(resolution) , m_keepAspect(true) { setCaption(i18n("Layer Size")); setObjectName(name); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgLayerSize(this); Q_CHECK_PTR(m_page); m_page->layout()->setMargin(0); m_page->setObjectName(name); _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); _widthUnitManager->setApparentUnitFromSymbol("px"); _heightUnitManager->setApparentUnitFromSymbol("px"); m_page->newWidthDouble->setUnitManager(_widthUnitManager); m_page->newHeightDouble->setUnitManager(_heightUnitManager); m_page->newWidthDouble->setDecimals(2); m_page->newHeightDouble->setDecimals(2); m_page->newWidthDouble->setDisplayUnit(false); m_page->newHeightDouble->setDisplayUnit(false); m_page->newWidthDouble->setValue(width); m_page->newWidthDouble->setFocus(); m_page->newHeightDouble->setValue(height); m_page->filterCmb->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); - m_page->filterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formatedDescriptions()); + m_page->filterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formattedDescriptions()); m_page->filterCmb->setCurrent("Bicubic"); m_page->newWidthUnit->setModel(_widthUnitManager); m_page->newHeightUnit->setModel(_heightUnitManager); const int pixelUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf("px"); //TODO: have a better way to identify units. m_page->newWidthUnit->setCurrentIndex(pixelUnitIndex); m_page->newHeightUnit->setCurrentIndex(pixelUnitIndex); m_page->aspectRatioBtn->setKeepAspectRatio(true); m_page->constrainProportionsCkb->setChecked(true); setMainWidget(m_page); connect(this, SIGNAL(okClicked()), this, SLOT(accept())); connect(m_page->aspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->newWidthDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotWidthChanged(double))); connect(m_page->newHeightDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotHeightChanged(double))); connect(m_page->newWidthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->newHeightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->newWidthUnit, SLOT(setCurrentIndex(int))); connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->newHeightUnit, SLOT(setCurrentIndex(int))); } DlgLayerSize::~DlgLayerSize() { delete m_page; } qint32 DlgLayerSize::width() { return (qint32)m_width; } qint32 DlgLayerSize::height() { return (qint32)m_height; } KisFilterStrategy *DlgLayerSize::filterType() { KoID filterID = m_page->filterCmb->currentItem(); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value(filterID.id()); return filter; } // SLOTS void DlgLayerSize::slotWidthChanged(double w) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = w*_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_width = qRound(resValue); if (m_keepAspect) { m_height = qRound(m_width / m_aspectRatio); m_page->newHeightDouble->blockSignals(true); m_page->newHeightDouble->changeValue(w / m_aspectRatio); m_page->newHeightDouble->blockSignals(false); } } void DlgLayerSize::slotHeightChanged(double h) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = h*_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_height = qRound(resValue); if (m_keepAspect) { m_width = qRound(m_height * m_aspectRatio); m_page->newWidthDouble->blockSignals(true); m_page->newWidthDouble->changeValue(h * m_aspectRatio); m_page->newWidthDouble->blockSignals(false); } } void DlgLayerSize::slotAspectChanged(bool keep) { m_page->aspectRatioBtn->blockSignals(true); m_page->constrainProportionsCkb->blockSignals(true); m_page->aspectRatioBtn->setKeepAspectRatio(keep); m_page->constrainProportionsCkb->setChecked(keep); m_page->aspectRatioBtn->blockSignals(false); m_page->constrainProportionsCkb->blockSignals(false); m_keepAspect = keep; if (keep) { // values may be out of sync, so we need to reset it to defaults m_width = m_originalWidth; m_height = m_originalHeight; updateWidthUIValue(m_width); updateHeightUIValue(m_height); } } void DlgLayerSize::updateWidthUIValue(double value) { m_page->newWidthDouble->blockSignals(true); const double resValue = value/_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_page->newWidthDouble->changeValue(resValue); m_page->newWidthDouble->blockSignals(false); } void DlgLayerSize::updateHeightUIValue(double value) { m_page->newHeightDouble->blockSignals(true); const double resValue = value/_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_page->newHeightDouble->changeValue(resValue); m_page->newHeightDouble->blockSignals(false); } diff --git a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop index 8fcb3817b5..6c744694b5 100644 --- a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop @@ -1,26 +1,32 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=assignprofiledialog X-Python-2-Compatible=false Name=Assign Profile to Image Name[ca]=Assigna un perfil a una imatge Name[ca@valencia]=Assigna un perfil a una imatge Name[cs]=Přiřadit obrázku profil +Name[en_GB]=Assign Profile to Image Name[es]=Asignar perfil a imagen Name[it]=Assegna profilo a immagine Name[nl]=Profiel aan afbeelding toekennen +Name[pl]=Przypisz profil do obrazu Name[pt]=Atribuir um Perfil à Imagem Name[sv]=Tilldela profil till bild +Name[tr]=Görüntüye Profil Ata Name[uk]=Призначити профіль до зображення Name[x-test]=xxAssign Profile to Imagexx Comment=Assign a profile to an image without converting it. Comment[ca]=Assigna un perfil a una imatge sense convertir-la. Comment[ca@valencia]=Assigna un perfil a una imatge sense convertir-la. +Comment[en_GB]=Assign a profile to an image without converting it. Comment[es]=Asignar un perfil a una imagen sin convertirla. Comment[it]=Assegna un profilo a un'immagine senza convertirla. Comment[nl]=Een profiel aan een afbeelding toekennen zonder het te converteren. +Comment[pl]=Przypisz profil do obrazu bez jego przekształcania. Comment[pt]=Atribui um perfil à imagem sem a converter. Comment[sv]=Tilldela en profil till en bild utan att konvertera den. +Comment[tr]=Bir görüntüye, görüntüyü değiştirmeden bir profil ata. Comment[uk]=Призначити профіль до зображення без його перетворення. Comment[x-test]=xxAssign a profile to an image without converting it.xx diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop index 22237086a8..3eace05f04 100644 --- a/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop @@ -1,27 +1,34 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=hello X-Python-2-Compatible=false Name=Hello World Name[ca]=Hola món Name[ca@valencia]=Hola món Name[cs]=Hello World +Name[en_GB]=Hello World Name[es]=Hola mundo +Name[fr]=Bonjour tout le monde Name[it]=Ciao mondo Name[nl]=Hallo wereld +Name[pl]=Witaj świecie Name[pt]=Olá Mundo Name[sv]=Hello World +Name[tr]=Merhaba Dünya Name[uk]=Привіт, світе Name[x-test]=xxHello Worldxx Comment=Basic plugin to test PyKrita Comment[ca]=Connector bàsic per provar el PyKrita Comment[ca@valencia]=Connector bàsic per provar el PyKrita Comment[cs]=Základní modul pro testování PyKrita +Comment[en_GB]=Basic plugin to test PyKrita Comment[es]=Complemento básico para probar PyKrita Comment[it]=Estensione di base per provare PyKrita Comment[nl]=Basisplug-in om PyKrita te testen +Comment[pl]=Podstawowa wtyczka do wypróbowania PyKrity Comment[pt]='Plugin' básico para testar o PyKrita Comment[sv]=Enkelt insticksprogram för att utprova PyKrita +Comment[tr]=PyKrita'yı test etmek için temel eklenti Comment[uk]=Базовий додаток для тестування PyKrita Comment[x-test]=xxBasic plugin to test PyKritaxx diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop b/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop index 372e509936..f796953a51 100644 --- a/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop @@ -1,28 +1,34 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=highpass X-Python-2-Compatible=false Name=Highpass Filter Name[ca]=Filtre passa-alt Name[ca@valencia]=Filtre passa-alt Name[cs]=Filtr s horní propustí Name[de]=Hochpassfilter +Name[en_GB]=Highpass Filter Name[es]=Filtro paso alto Name[it]=Filtro di accentuazione passaggio Name[nl]=Hoogdoorlaatfilter +Name[pl]=Filtr górnoprzepustowy Name[pt]=Filtro Passa-Alto Name[sv]=Högpassfilter +Name[tr]=Yüksek Geçirgen Süzgeç Name[uk]=Високочастотний фільтр Name[x-test]=xxHighpass Filterxx Comment=Highpass Filter, based on http://registry.gimp.org/node/7385 Comment[ca]=Filtre passa-alt, basat en el http://registry.gimp.org/node/7385 Comment[ca@valencia]=Filtre passa-alt, basat en el http://registry.gimp.org/node/7385 Comment[de]=Hochpassfilter, Grundlage ist http://registry.gimp.org/node/7385 +Comment[en_GB]=Highpass Filter, based on http://registry.gimp.org/node/7385 Comment[es]=Filtro paso alto, basado en http://registry.gimp.org/node/7385 Comment[it]=Filtro di accentuazione passaggio, basato su http://registry.gimp.org/node/7385 Comment[nl]=Hoogdoorlaatfilter, gebaseerd op http://registry.gimp.org/node/7385 +Comment[pl]=Filtr górnoprzepustowy, oparty na http://registry.gimp.org/node/7385 Comment[pt]=Filtro passa-alto, baseado em http://registry.gimp.org/node/7385 Comment[sv]=Högpassfilter, baserat på http://registry.gimp.org/node/7385 +Comment[tr]=http://registry.gimp.org/node/7385 tabanlı Yüksek Geçirgen Süzgeç Comment[uk]=Високочастотний фільтр, засновано на on http://registry.gimp.org/node/7385 Comment[x-test]=xxHighpass Filter, based on http://registry.gimp.org/node/7385xx diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop b/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop index 7fac2d24cc..8dce2bdee2 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop @@ -1,26 +1,32 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scripter X-Python-2-Compatible=false Name=Scripter Name[ca]=Scripter Name[ca@valencia]=Scripter Name[de]=Scripter +Name[en_GB]=Scripter Name[es]=Guionador Name[it]=Scripter Name[nl]=Scriptschrijver +Name[pl]=Skrypter Name[pt]=Programador Name[sv]=Skriptgenerator +Name[tr]=Betik yazarı Name[uk]=Скриптер Name[x-test]=xxScripterxx Comment=Plugin to execute ad-hoc Python code Comment[ca]=Connector per executar codi Python ad-hoc Comment[ca@valencia]=Connector per executar codi Python ad-hoc +Comment[en_GB]=Plugin to execute ad-hoc Python code Comment[es]=Complemento para ejecutar código Python a medida Comment[it]=Estensione per eseguire ad-hoc codice Python Comment[nl]=Plug-in om ad-hoc Python code uit te voeren +Comment[pl]=Wtyczka do wykonywania kodu Pythona ad-hoc Comment[pt]='Plugin' para executar código em Python arbitrário Comment[sv]=Insticksprogram för att köra godtycklig Python-kod +Comment[tr]=Geçici Python kodu çalıştırmak için eklenti Comment[uk]=Додаток для виконання апріорного коду Python Comment[x-test]=xxPlugin to execute ad-hoc Python codexx diff --git a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop index e439689d64..9c1fcb44a2 100644 --- a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop @@ -1,26 +1,32 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=selectionsbagdocker X-Python-2-Compatible=false Name=Selections Bag Docker Name[ca]=Acoblador de bossa de seleccions Name[ca@valencia]=Acoblador de bossa de seleccions +Name[en_GB]=Selections Bag Docker Name[es]=Panel de selecciones Name[it]=Area di raccolta selezioni Name[nl]=Docker van zak met selecties +Name[pl]=Dok worka zaznaczeń Name[pt]=Área de Selecções Name[sv]=Dockningspanel med markeringspåse +Name[tr]=Seçim Çantası İşçisi Name[uk]=Бічна панель позначеного Name[x-test]=xxSelections Bag Dockerxx Comment=A docker that allow to store a list of selections Comment[ca]=Un acoblador que permet emmagatzemar una llista de seleccions Comment[ca@valencia]=Un acoblador que permet emmagatzemar una llista de seleccions Comment[cs]=Dok umožňující uložit seznam výběrů +Comment[en_GB]=A docker that allow to store a list of selections Comment[es]=Un panel que permite guardar una lista de selecciones Comment[it]=Un'area di aggancio che consente di memorizzare un elenco di selezioni Comment[nl]=Een docker die een lijst met selecties kan opslaan +Comment[pl]=Dok, który umożliwia przechowywanie listy zaznaczeń Comment[pt]=Uma área acoplável que permite guardar uma lista de selecções Comment[sv]=En dockningspanel som gör det möjligt att lagra en lista över markeringar +Comment[tr]=Seçimlerin bir listesini saklamayı sağlayan işçi Comment[uk]=Бічна панель, на якій можна зберігати список позначеного Comment[x-test]=xxA docker that allow to store a list of selectionsxx diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop index ef420225e8..25986a3e45 100644 --- a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop @@ -1,26 +1,32 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenbrushes X-Python-2-Compatible=false Name=Ten Brushes Name[ca]=Deu pinzells Name[ca@valencia]=Deu pinzells Name[cs]=Deset štětců +Name[en_GB]=Ten Brushes Name[es]=Diez pinceles Name[it]=Dieci pennelli Name[nl]=Tien penselen +Name[pl]=Dziesięć pędzli Name[pt]=Dez Pincéis Name[sv]=Tio penslar +Name[tr]=On Fırça Name[uk]=Десять пензлів Name[x-test]=xxTen Brushesxx Comment=Assign a preset to ctrl-1 to ctrl-0 Comment[ca]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[ca@valencia]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 +Comment[en_GB]=Assign a preset to ctrl-1 to ctrl-0 Comment[es]=Asignar una preselección a Ctrl-1 hasta Ctrl-0 Comment[it]=Assegna una preimpostazione per ctrl-1 a ctrl-0 Comment[nl]=Een voorinstelling toekennen aan Ctrl-1 tot Ctrl-0 +Comment[pl]=Przypisz nastawę do ctrl-1 lub ctrl-0 Comment[pt]=Atribuir uma predefinição de Ctrl-1 a Ctrl-0 Comment[sv]=Tilldela en förinställning för Ctrl+1 till Ctrl+0 +Comment[tr]= ctrl-1 ve ctrl-0 için ayar atama Comment[uk]=Прив’язування наборів налаштувань до скорочень від ctrl-1 до ctrl-0 Comment[x-test]=xxAssign a preset to ctrl-1 to ctrl-0xx diff --git a/plugins/extensions/pykrita/sip/CMakeLists.txt b/plugins/extensions/pykrita/sip/CMakeLists.txt index 9b3a12794d..beb7a1f6b7 100644 --- a/plugins/extensions/pykrita/sip/CMakeLists.txt +++ b/plugins/extensions/pykrita/sip/CMakeLists.txt @@ -1,16 +1,27 @@ include(SIPMacros) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../libkis) -set(SIP_INCLUDES ${PYQT_SIP_DIR_OVERRIDE} ./krita) +message( ${SIP_VERSION} " - The version of SIP found expressed as a 6 digit hex number suitable for comparison as a string.") +message( ${SIP_VERSION_STR} " - The version of SIP found as a human readable string.") +message( ${SIP_EXECUTABLE} " - Path and filename of the SIP command line executable.") +message( ${SIP_INCLUDE_DIR} " - Directory holding the SIP C++ header file.") +message( ${SIP_DEFAULT_SIP_DIR} " - default SIP dir" ) + +set(SIP_INCLUDES + ${SIP_DEFAULT_SIP_DIR} + ${SIP_DEFAULT_SIP_DIR}/PyQt5 + ${PYQT_SIP_DIR_OVERRIDE} + ./krita) + set(SIP_CONCAT_PARTS 1) set(SIP_TAGS ALL WS_X11 ${PYQT5_VERSION_TAG}) set(SIP_EXTRA_OPTIONS -g -x PyKDE_QVector) set(PYTHON_SITE_PACKAGES_INSTALL_DIR ${DATA_INSTALL_DIR}/krita/pykrita/) add_sip_python_module(PyKrita.krita ./krita/kritamod.sip kritalibkis kritaui kritaimage kritalibbrush) #install(FILES # ./__init__.py # DESTINATION ${PYTHON_SITE_PACKAGES_INSTALL_DIR}) diff --git a/plugins/extensions/pykrita/sip/krita/Document.sip b/plugins/extensions/pykrita/sip/krita/Document.sip index 36d3b80539..a96ce283a8 100644 --- a/plugins/extensions/pykrita/sip/krita/Document.sip +++ b/plugins/extensions/pykrita/sip/krita/Document.sip @@ -1,62 +1,65 @@ class Document : QObject /NoDefaultCtors/ { %TypeHeaderCode #include "Document.h" %End Document(const Document & __0); public: bool operator==(const Document &other) const; bool operator!=(const Document &other) const; public Q_SLOTS: Node * activeNode() const /Factory/; void setActiveNode(Node* value); QList topLevelNodes() const /Factory/; Node *nodeByName(const QString &node) const /Factory/; bool batchmode() const; void setBatchmode(bool value); QString colorDepth() const; QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); bool setColorSpace(const QString &value, const QString &colorDepth, const QString &colorProfile); QString documentInfo() const; void setDocumentInfo(const QString &document); QString fileName() const; void setFileName(QString value); int height() const; void setHeight(int value); QString name() const; void setName(QString value); int resolution() const; void setResolution(int value); Node * rootNode() const /Factory/; Selection * selection() const /Factory/; void setSelection(Selection* value); int width() const; void setWidth(int value); double xRes() const; void setXRes(double xRes) const; double yRes() const; void setYRes(double yRes) const; QByteArray pixelData(int x, int y, int w, int h) const; bool close(); void crop(int x, int y, int w, int h); bool exportImage(const QString &filename, const InfoObject & exportConfiguration); void flatten(); void resizeImage(int w, int h); + void scaleImage(int w, int h, int xres, int yres, QString strategy); + void rotateImage(double radians); + void shearImage(double angleX, double angleY); bool save(); bool saveAs(const QString & filename); Node *createNode(const QString & name, const QString & nodeType) /Factory/; QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const; QImage thumbnail(int w, int h) const; void lock(); void unlock(); void waitForDone(); bool tryBarrierLock(); bool isIdle(); void refreshProjection(); private: }; diff --git a/plugins/extensions/pykrita/sip/krita/Krita.sip b/plugins/extensions/pykrita/sip/krita/Krita.sip index 07677e84ec..98d2cb8d49 100644 --- a/plugins/extensions/pykrita/sip/krita/Krita.sip +++ b/plugins/extensions/pykrita/sip/krita/Krita.sip @@ -1,57 +1,58 @@ class Krita : QObject { %TypeHeaderCode #include "Krita.h" %End public: public Q_SLOTS: Krita(QObject* parent /TransferThis/ = 0); virtual ~Krita(); Document * activeDocument() const /Factory/; void setActiveDocument(Document* value); bool batchmode() const; void setBatchmode(bool value); QList actions() const /Factory/; Action * action(const QString & name) const; QList documents() const /Factory/; QStringList filters() const; Filter * filter(const QString &name) const /Factory/; + QStringList filterStrategies() const; QStringList profiles(const QString &colorModel, const QString &ColorDepth) const; bool addProfile(const QString &profilePath); Notifier * notifier() const; QString version() const; QList views() const /Factory/; Window * activeWindow() const /Factory/; QList windows() const /Factory/; QMap resources(const QString &type) const /Factory/; Document * createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile) /Factory/; Document * openDocument(const QString &filename) /Factory/; Window * openWindow(); Action * createAction(const QString & text); void addExtension(Extension* _extension /GetWrapper/); %MethodCode Py_BEGIN_ALLOW_THREADS sipCpp->addExtension(a0); Py_END_ALLOW_THREADS sipTransferTo(a0Wrapper, Py_None); %End void addDockWidgetFactory(DockWidgetFactoryBase* _factory /GetWrapper/); %MethodCode Py_BEGIN_ALLOW_THREADS sipCpp->addDockWidgetFactory(a0); Py_END_ALLOW_THREADS sipTransferTo(a0Wrapper, Py_None); %End void writeSetting(const QString &group, const QString &name, const QString &value); QString readSetting(const QString &group, const QString &name, const QString &defaultValue); static Krita * instance(); static QObject * fromVariant(const QVariant & v); private: Krita(const Krita &); // Generated }; diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip index e4c7380d36..1e59d02353 100644 --- a/plugins/extensions/pykrita/sip/krita/Node.sip +++ b/plugins/extensions/pykrita/sip/krita/Node.sip @@ -1,57 +1,62 @@ class Node : QObject { %TypeHeaderCode #include "Node.h" %End Node(const Node & __0); public: virtual ~Node(); bool operator==(const Node &other) const; bool operator!=(const Node &other) const; public Q_SLOTS: bool alphaLocked() const; void setAlphaLocked(bool value); QString blendingMode() const; void setBlendingMode(QString value); QList channels() const; QList childNodes() const; bool addChildNode(Node *child, Node *above); bool removeChildNode(Node *child); void setChildNodes(QList nodes); QString colorDepth() const; bool animated() const; void enableAnimation() const; void setCollapsed(bool collapsed); bool collapsed() const; int colorLabel() const; void setColorLabel(int value); QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); bool inheritAlpha() const; void setInheritAlpha(bool value); bool locked() const; void setLocked(bool value); QString name() const; void setName(QString value); int opacity() const; void setOpacity(int value); Node * parentNode() const /Factory/; QString type() const; bool visible() const; void setVisible(bool value); QByteArray pixelData(int x, int y, int w, int h) const; + QByteArray pixelDataAtTime(int x, int y, int w, int h, int time) const; QByteArray projectionPixelData(int x, int y, int w, int h) const; void setPixelData(QByteArray value, int x, int y, int w, int h); QRect bounds() const; void move(int x, int y); QPoint position() const; bool remove(); Node *duplicate() /Factory/; void save(const QString &filename, double xRes, double yRes); Node *mergeDown() /Factory/; + void scaleNode(int width, int height, QString strategy); + void rotateNode(double radians); + void cropNode(int x, int y, int w, int h); + void shearNode(double angleX, double angleY); QImage thumbnail(int w, int h); Q_SIGNALS: private: }; diff --git a/plugins/filters/blur/kis_wdg_gaussian_blur.cpp b/plugins/filters/blur/kis_wdg_gaussian_blur.cpp index 7e347681a6..e3d7ead13c 100644 --- a/plugins/filters/blur/kis_wdg_gaussian_blur.cpp +++ b/plugins/filters/blur/kis_wdg_gaussian_blur.cpp @@ -1,119 +1,119 @@ /* * This file is part of Krita * * Copyright (c) 2009 Edward Apap * * 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_wdg_gaussian_blur.h" #include #include #include #include #include #include #include #include #include "ui_wdg_gaussian_blur.h" KisWdgGaussianBlur::KisWdgGaussianBlur(QWidget * parent) : KisConfigWidget(parent) { m_widget = new Ui_WdgGaussianBlur(); m_widget->setupUi(this); m_widget->aspectButton->setKeepAspectRatio(false); - m_widget->horizontalRadius->setRange(0.0, 100.0, 2); + m_widget->horizontalRadius->setRange(0.0, 1000.0, 2); m_widget->horizontalRadius->setSingleStep(0.2); m_widget->horizontalRadius->setValue(0.5); m_widget->horizontalRadius->setSuffix(i18n(" px")); connect(m_widget->horizontalRadius, SIGNAL(valueChanged(qreal)), this, SLOT(horizontalRadiusChanged(qreal))); - m_widget->verticalRadius->setRange(0.0, 100.0, 2); + m_widget->verticalRadius->setRange(0.0, 1000.0, 2); m_widget->verticalRadius->setSingleStep(0.2); m_widget->verticalRadius->setValue(0.5); m_widget->verticalRadius->setSuffix(i18n(" px")); connect(m_widget->verticalRadius, SIGNAL(valueChanged(qreal)), this, SLOT(verticalRadiusChanged(qreal))); connect(m_widget->aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(aspectLockChanged(bool))); connect(m_widget->horizontalRadius, SIGNAL(valueChanged(qreal)), SIGNAL(sigConfigurationItemChanged())); connect(m_widget->verticalRadius, SIGNAL(valueChanged(qreal)), SIGNAL(sigConfigurationItemChanged())); } KisWdgGaussianBlur::~KisWdgGaussianBlur() { delete m_widget; } KisPropertiesConfigurationSP KisWdgGaussianBlur::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("gaussian blur", 1); config->setProperty("horizRadius", m_widget->horizontalRadius->value()); config->setProperty("vertRadius", m_widget->verticalRadius->value()); config->setProperty("lockAspect", m_widget->aspectButton->keepAspectRatio()); return config; } void KisWdgGaussianBlur::setConfiguration(const KisPropertiesConfigurationSP config) { QVariant value; if (config->getProperty("horizRadius", value)) { m_widget->horizontalRadius->setValue(value.toFloat()); } if (config->getProperty("vertRadius", value)) { m_widget->verticalRadius->setValue(value.toFloat()); } if (config->getProperty("lockAspect", value)) { m_widget->aspectButton->setKeepAspectRatio(value.toBool()); } } void KisWdgGaussianBlur::horizontalRadiusChanged(qreal v) { m_widget->horizontalRadius->blockSignals(true); m_widget->horizontalRadius->setValue(v); m_widget->horizontalRadius->blockSignals(false); if (m_widget->aspectButton->keepAspectRatio()) { m_widget->verticalRadius->blockSignals(true); m_widget->verticalRadius->setValue(v); m_widget->verticalRadius->blockSignals(false); } } void KisWdgGaussianBlur::verticalRadiusChanged(qreal v) { m_widget->verticalRadius->blockSignals(true); m_widget->verticalRadius->setValue(v); m_widget->verticalRadius->blockSignals(false); if (m_widget->aspectButton->keepAspectRatio()) { m_widget->horizontalRadius->blockSignals(true); m_widget->horizontalRadius->setValue(v); m_widget->horizontalRadius->blockSignals(false); } } void KisWdgGaussianBlur::aspectLockChanged(bool v) { if (v) { m_widget->verticalRadius->setValue( m_widget->horizontalRadius->value() ); } } diff --git a/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui b/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui index 551e277883..f9eaa7556b 100644 --- a/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui +++ b/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui @@ -1,238 +1,247 @@ WdgHSVAdjustment 0 0 - 406 + 412 188 0 0 0 0 5 Qt::Vertical 20 0 &Type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter cmbType &Colorize 0 Hue/Saturation/Value Hue/Saturation/Lightness Hue/Saturation/Intensity Hue/Saturation/Luma Blue Chroma/Red Chroma/Luma -180 180 Qt::Horizontal -100 100 - Value: + &Value: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + valueSlider + -100 100 0 true Qt::Horizontal false false QSlider::NoTicks 0 0 &Saturation: false Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + saturationSlider + 0 0 &Hue: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + hueSlider + -100 100 Qt::Horizontal -100 100 -180 180 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
cmbType chkColorize
diff --git a/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h b/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h index 2d34d2ea70..82889a3666 100644 --- a/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h +++ b/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h @@ -1,129 +1,129 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SVGTEXTHELPER_H #define SVGTEXTHELPER_H #include #include #include #include typedef QList CharTransforms; -class KoXmlElement; +#include class SvgGraphicsContext; class ArtisticTextLoadingContext { public: enum OffsetType { None, Absolute, Relative }; ArtisticTextLoadingContext(); static QString simplifyText(const QString &text, bool preserveWhiteSpace = false); /// Parses current character transforms (x,y,dx,dy,rotate) void parseCharacterTransforms(const KoXmlElement &element, SvgGraphicsContext *gc); /// Pushes the current character transforms to the stack void pushCharacterTransforms(); /// Pops last character transforms from the stack void popCharacterTransforms(); /// Checks current x-offset type OffsetType xOffsetType() const; /// Checks current y-offset type OffsetType yOffsetType() const; /// Returns x-offsets from stack CharTransforms xOffsets(int count); /// Returns y-offsets from stack CharTransforms yOffsets(int count); /// Returns rotations from stack CharTransforms rotations(int count); /// Returns the text position QPointF textPosition() const; private: void printDebug(); struct CharTransformState { CharTransformState() : hasData(false), lastTransform(0.0) { } CharTransformState(const CharTransforms &initialData) : data(initialData), hasData(!initialData.isEmpty()) , lastTransform(initialData.isEmpty() ? 0.0 : initialData.last()) { } CharTransforms extract(int count) { const int copyCount = qMin(data.count(), count); CharTransforms extracted = data.mid(0, copyCount); data = data.mid(copyCount); return extracted; } CharTransforms data; bool hasData; qreal lastTransform; }; typedef QList CharTransformStack; enum ValueType { Number, XLength, YLength }; /// Parses offset values from the given string CharTransforms parseList(const QString &listString, SvgGraphicsContext *gc, ValueType type); /// Collects number of specified transforms values from the stack CharTransforms collectValues(int count, CharTransformState ¤t, CharTransformStack &stack); CharTransformState m_currentAbsolutePosX; ///< current absolute character x-positions CharTransformState m_currentAbsolutePosY; ///< current absolute character y-positions CharTransformState m_currentRelativePosX; ///< current relative character x-positions CharTransformState m_currentRelativePosY; ///< current relative character y-positions CharTransformState m_currentRotations; ///< current character rotations CharTransformStack m_absolutePosX; ///< stack of absolute character x-positions CharTransformStack m_absolutePosY; ///< stack of absolute character y-positions CharTransformStack m_relativePosX; ///< stack of relative character x-positions CharTransformStack m_relativePosY; ///< stack of relative character y-positions CharTransformStack m_rotations; ///< stack of character rotations QPointF m_textPosition; }; #endif // SVGTEXTHELPER_H diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShapeFactory.cpp b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShapeFactory.cpp index a549de1ef4..fa2bfe4a77 100644 --- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShapeFactory.cpp +++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShapeFactory.cpp @@ -1,568 +1,568 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "enhancedpath/EnhancedPathShapeFactory.h" #include "enhancedpath/EnhancedPathShape.h" #include #include #include #include #include #include #include #include #include "kis_pointer_utils.h" #include #include EnhancedPathShapeFactory::EnhancedPathShapeFactory() : KoShapeFactoryBase(EnhancedPathShapeId, i18n("An enhanced path shape")) { setToolTip(i18n("An enhanced path")); setIconName(koIconNameCStr("enhancedpath")); setXmlElementNames(KoXmlNS::draw, QStringList("custom-shape")); setLoadingPriority(1); - addCross(); - addArrow(); - addCallout(); - addSmiley(); - addCircularArrow(); - addGearhead(); +// addCross(); +// addArrow(); +// addCallout(); +// addSmiley(); +// addCircularArrow(); +// addGearhead(); } KoShape *EnhancedPathShapeFactory::createDefaultShape(KoDocumentResourceManager *) const { EnhancedPathShape *shape = new EnhancedPathShape(QRect(0, 0, 100, 100)); shape->setStroke(toQShared(new KoShapeStroke(1.0))); shape->setShapeId(KoPathShapeId); shape->addModifiers("35"); shape->addFormula("Right", "width - $0"); shape->addFormula("Bottom", "height - $0"); shape->addFormula("Half", "min(0.5 * height, 0.5 * width)"); shape->addCommand("M $0 0"); shape->addCommand("L ?Right 0 ?Right $0 width $0 width ?Bottom ?Right ?Bottom"); shape->addCommand("L ?Right height $0 height $0 ?Bottom 0 ?Bottom 0 $0 $0 $0"); shape->addCommand("Z"); ComplexType handle; handle["draw:handle-position"] = "$0 0"; handle["draw:handle-range-x-minimum"] = '0'; handle["draw:handle-range-x-maximum"] = "?Half"; shape->addHandle(handle); shape->setSize(QSize(100, 100)); return shape; } KoShape *EnhancedPathShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *) const { QVariant viewboxData; const QRect viewBox = (params->property(QLatin1String("viewBox"), viewboxData)) ? viewboxData.toRect() : QRect(0, 0, 100, 100); EnhancedPathShape *shape = new EnhancedPathShape(viewBox); shape->setShapeId(KoPathShapeId); shape->setStroke(toQShared(new KoShapeStroke(1.0))); shape->addModifiers(params->stringProperty("modifiers")); ListType handles = params->property("handles").toList(); foreach (const QVariant &v, handles) { shape->addHandle(v.toMap()); } ComplexType formulae = params->property("formulae").toMap(); ComplexType::const_iterator formula = formulae.constBegin(); ComplexType::const_iterator lastFormula = formulae.constEnd(); for (; formula != lastFormula; ++formula) { shape->addFormula(formula.key(), formula.value().toString()); } QStringList commands = params->property("commands").toStringList(); foreach (const QString &cmd, commands) { shape->addCommand(cmd); } QVariant color; if (params->property("background", color)) { shape->setBackground(QSharedPointer(new KoColorBackground(color.value()))); } QSizeF size = shape->size(); if (size.width() > size.height()) { shape->setSize(QSizeF(100, 100 * size.height() / size.width())); } else { shape->setSize(QSizeF(100 * size.width() / size.height(), 100)); } return shape; } KoProperties *EnhancedPathShapeFactory::dataToProperties( const QString &modifiers, const QStringList &commands, const ListType &handles, const ComplexType &formulae) const { KoProperties *props = new KoProperties(); props->setProperty("modifiers", modifiers); props->setProperty("commands", commands); props->setProperty("handles", handles); props->setProperty("formulae", formulae); props->setProperty("background", QVariant::fromValue(QColor(Qt::red))); return props; } void EnhancedPathShapeFactory::addCross() { QString modifiers("35"); QStringList commands; commands.append("M $0 0"); commands.append("L ?Right 0 ?Right $0 width $0 width ?Bottom ?Right ?Bottom"); commands.append("L ?Right height $0 height $0 ?Bottom 0 ?Bottom 0 $0 $0 $0"); commands.append("Z"); ListType handles; ComplexType handle; handle["draw:handle-position"] = "$0 0"; handle["draw:handle-range-x-minimum"] = '0'; handle["draw:handle-range-x-maximum"] = "?Half"; handles.append(QVariant(handle)); ComplexType formulae; formulae["Right"] = "width - $0"; formulae["Bottom"] = "height - $0"; formulae["Half"] = "min(0.5 * height, 0.5 * width)"; KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "cross"; t.name = i18n("Cross"); t.family = "funny"; t.toolTip = i18n("A cross"); t.iconName = koIconName("cross-shape"); t.properties = dataToProperties(modifiers, commands, handles, formulae); addTemplate(t); } void EnhancedPathShapeFactory::addArrow() { { // arrow right QString modifiers("60 35"); QStringList commands; commands.append("M $0 $1"); commands.append("L $0 0 width ?HalfHeight $0 height $0 ?LowerCorner 0 ?LowerCorner 0 $1"); commands.append("Z"); ListType handles; ComplexType handle; handle["draw:handle-position"] = "$0 $1"; handle["draw:handle-range-x-minimum"] = '0'; handle["draw:handle-range-x-maximum"] = "width"; handle["draw:handle-range-y-minimum"] = '0'; handle["draw:handle-range-y-maximum"] = "?HalfHeight"; handles.append(QVariant(handle)); ComplexType formulae; formulae["HalfHeight"] = "0.5 * height"; formulae["LowerCorner"] = "height - $1"; KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "arrow_right"; t.name = i18n("Arrow"); t.family = "arrow"; t.toolTip = i18n("An arrow"); t.iconName = koIconName("draw-arrow-forward"); t.properties = dataToProperties(modifiers, commands, handles, formulae); addTemplate(t); } { // arrow left QString modifiers("40 35"); QStringList commands; commands.append("M $0 $1"); commands.append("L $0 0 0 ?HalfHeight $0 height $0 ?LowerCorner width ?LowerCorner width $1"); commands.append("Z"); ListType handles; ComplexType handle; handle["draw:handle-position"] = "$0 $1"; handle["draw:handle-range-x-minimum"] = '0'; handle["draw:handle-range-x-maximum"] = "width"; handle["draw:handle-range-y-minimum"] = '0'; handle["draw:handle-range-y-maximum"] = "?HalfHeight"; handles.append(QVariant(handle)); ComplexType formulae; formulae["HalfHeight"] = "0.5 * height"; formulae["LowerCorner"] = "height - $1"; KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "arrow_left"; t.name = i18n("Arrow"); t.family = "arrow"; t.toolTip = i18n("An arrow"); t.iconName = koIconName("draw-arrow-back"); t.properties = dataToProperties(modifiers, commands, handles, formulae); addTemplate(t); } { // arrow top QString modifiers("35 40"); QStringList commands; commands.append("M $0 $1"); commands.append("L 0 $1 ?HalfWidth 0 width $1 ?RightCorner $1 ?RightCorner height $0 height"); commands.append("Z"); ListType handles; ComplexType handle; handle["draw:handle-position"] = "$0 $1"; handle["draw:handle-range-x-minimum"] = '0'; handle["draw:handle-range-x-maximum"] = "?HalfWidth"; handle["draw:handle-range-y-minimum"] = '0'; handle["draw:handle-range-y-maximum"] = "height"; handles.append(QVariant(handle)); ComplexType formulae; formulae["HalfWidth"] = "0.5 * width"; formulae["RightCorner"] = "width - $0"; KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "arrow_top"; t.name = i18n("Arrow"); t.family = "arrow"; t.toolTip = i18n("An arrow"); t.iconName = koIconName("draw-arrow-up"); t.properties = dataToProperties(modifiers, commands, handles, formulae); addTemplate(t); } { // arrow bottom QString modifiers("35 60"); QStringList commands; commands.append("M $0 $1"); commands.append("L 0 $1 ?HalfWidth height width $1 ?RightCorner $1 ?RightCorner 0 $0 0"); commands.append("Z"); ListType handles; ComplexType handle; handle["draw:handle-position"] = "$0 $1"; handle["draw:handle-range-x-minimum"] = '0'; handle["draw:handle-range-x-maximum"] = "?HalfWidth"; handle["draw:handle-range-y-minimum"] = '0'; handle["draw:handle-range-y-maximum"] = "height"; handles.append(QVariant(handle)); ComplexType formulae; formulae["HalfWidth"] = "0.5 * width"; formulae["RightCorner"] = "width - $0"; KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "arrow_bottom"; t.name = i18n("Arrow"); t.family = "arrow"; t.toolTip = i18n("An arrow"); t.iconName = koIconName("draw-arrow-down"); t.properties = dataToProperties(modifiers, commands, handles, formulae); addTemplate(t); } } void EnhancedPathShapeFactory::addCallout() { QString modifiers("4250 45000"); QStringList commands; commands.append("M 3590 0"); commands.append("X 0 3590"); commands.append("L ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010"); commands.append("Y 3590 21600"); commands.append("L ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600"); commands.append("X 21600 18010"); commands.append("L ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590"); commands.append("Y 18010 0"); commands.append("L ?f14 ?f15 12630 0 8970 0 ?f16 ?f17"); commands.append("Z"); commands.append("N"); ComplexType formulae; formulae["f0"] = "$0 -10800"; formulae["f1"] = "$1 -10800"; formulae["f2"] = "if(?f18 ,$0 ,0)"; formulae["f3"] = "if(?f18 ,$1 ,6280)"; formulae["f4"] = "if(?f23 ,$0 ,0)"; formulae["f5"] = "if(?f23 ,$1 ,15320)"; formulae["f6"] = "if(?f26 ,$0 ,6280)"; formulae["f7"] = "if(?f26 ,$1 ,21600)"; formulae["f8"] = "if(?f29 ,$0 ,15320)"; formulae["f9"] = "if(?f29 ,$1 ,21600)"; formulae["f10"] = "if(?f32 ,$0 ,21600)"; formulae["f11"] = "if(?f32 ,$1 ,15320)"; formulae["f12"] = "if(?f34 ,$0 ,21600)"; formulae["f13"] = "if(?f34 ,$1 ,6280)"; formulae["f14"] = "if(?f36 ,$0 ,15320)"; formulae["f15"] = "if(?f36 ,$1 ,0)"; formulae["f16"] = "if(?f38 ,$0 ,6280)"; formulae["f17"] = "if(?f38 ,$1 ,0)"; formulae["f18"] = "if($0 ,-1,?f19)"; formulae["f19"] = "if(?f1 ,-1,?f22)"; formulae["f20"] = "abs(?f0)"; formulae["f21"] = "abs(?f1)"; formulae["f22"] = "?f20 -?f21"; formulae["f23"] = "if($0 ,-1,?f24)"; formulae["f24"] = "if(?f1 ,?f22 ,-1)"; formulae["f25"] = "$1 -21600"; formulae["f26"] = "if(?f25 ,?f27 ,-1)"; formulae["f27"] = "if(?f0 ,-1,?f28)"; formulae["f28"] = "?f21 -?f20"; formulae["f29"] = "if(?f25 ,?f30 ,-1)"; formulae["f30"] = "if(?f0 ,?f28 ,-1)"; formulae["f31"] = "$0 -21600"; formulae["f32"] = "if(?f31 ,?f33 ,-1)"; formulae["f33"] = "if(?f1 ,?f22 ,-1)"; formulae["f34"] = "if(?f31 ,?f35 ,-1)"; formulae["f35"] = "if(?f1 ,-1,?f22)"; formulae["f36"] = "if($1 ,-1,?f37)"; formulae["f37"] = "if(?f0 ,?f28 ,-1)"; formulae["f38"] = "if($1 ,-1,?f39)"; formulae["f39"] = "if(?f0 ,-1,?f28)"; formulae["f40"] = "$0"; formulae["f41"] = "$1"; ListType handles; ComplexType handle; handle["draw:handle-position"] = "$0 $1"; handles.append(QVariant(handle)); KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "callout"; t.name = i18n("Callout"); t.family = "funny"; t.toolTip = i18n("A callout"); t.iconName = koIconName("callout-shape"); KoProperties *properties = dataToProperties(modifiers, commands, handles, formulae); properties->setProperty(QLatin1String("viewBox"), QRect(0, 0, 21600, 21600)); t.properties = properties; addTemplate(t); } void EnhancedPathShapeFactory::addSmiley() { QString modifiers("17520"); QStringList commands; commands.append("U 10800 10800 10800 10800 0 23592960"); commands.append("Z"); commands.append("N"); commands.append("U 7305 7515 1165 1165 0 23592960"); commands.append("Z"); commands.append("N"); commands.append("U 14295 7515 1165 1165 0 23592960"); commands.append("Z"); commands.append("N"); commands.append("M 4870 ?f1"); commands.append("C 8680 ?f2 12920 ?f2 16730 ?f1"); commands.append("Z"); commands.append("F"); commands.append("N"); ComplexType formulae; formulae["f0"] = "$0 -15510"; formulae["f1"] = "17520-?f0"; formulae["f2"] = "15510+?f0"; ListType handles; ComplexType handle; handle["draw:handle-position"] = "10800 $0"; handle["draw:handle-range-y-minimum"] = "15510"; handle["draw:handle-range-y-maximum"] = "17520"; handles.append(QVariant(handle)); KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "smiley"; t.name = i18n("Smiley"); t.family = "funny"; t.toolTip = i18n("Smiley"); t.iconName = koIconName("smiley-shape"); KoProperties *properties = dataToProperties(modifiers, commands, handles, formulae); properties->setProperty(QLatin1String("viewBox"), QRect(0, 0, 21600, 21600)); t.properties = properties; addTemplate(t); } void EnhancedPathShapeFactory::addCircularArrow() { QString modifiers("180 0 5500"); QStringList commands; commands.append("B ?f3 ?f3 ?f20 ?f20 ?f19 ?f18 ?f17 ?f16"); commands.append("W 0 0 21600 21600 ?f9 ?f8 ?f11 ?f10"); commands.append("L ?f24 ?f23 ?f36 ?f35 ?f29 ?f28"); commands.append("Z"); commands.append("N"); ComplexType formulae; formulae["f0"] = "$0"; formulae["f1"] = "$1"; formulae["f2"] = "$2"; formulae["f3"] = "10800+$2"; formulae["f4"] = "10800*sin($0 *(pi/180))"; formulae["f5"] = "10800*cos($0 *(pi/180))"; formulae["f6"] = "10800*sin($1 *(pi/180))"; formulae["f7"] = "10800*cos($1 *(pi/180))"; formulae["f8"] = "?f4 +10800"; formulae["f9"] = "?f5 +10800"; formulae["f10"] = "?f6 +10800"; formulae["f11"] = "?f7 +10800"; formulae["f12"] = "?f3 *sin($0 *(pi/180))"; formulae["f13"] = "?f3 *cos($0 *(pi/180))"; formulae["f14"] = "?f3 *sin($1 *(pi/180))"; formulae["f15"] = "?f3 *cos($1 *(pi/180))"; formulae["f16"] = "?f12 +10800"; formulae["f17"] = "?f13 +10800"; formulae["f18"] = "?f14 +10800"; formulae["f19"] = "?f15 +10800"; formulae["f20"] = "21600-?f3"; formulae["f21"] = "13500*sin($1 *(pi/180))"; formulae["f22"] = "13500*cos($1 *(pi/180))"; formulae["f23"] = "?f21 +10800"; formulae["f24"] = "?f22 +10800"; formulae["f25"] = "$2 -2700"; formulae["f26"] = "?f25 *sin($1 *(pi/180))"; formulae["f27"] = "?f25 *cos($1 *(pi/180))"; formulae["f28"] = "?f26 +10800"; formulae["f29"] = "?f27 +10800"; formulae["f30"] = "($1+45)*pi/180"; formulae["f31"] = "sqrt(((?f29-?f24)*(?f29-?f24))+((?f28-?f23)*(?f28-?f23)))"; formulae["f32"] = "sqrt(2)/2*?f31"; formulae["f33"] = "?f32*sin(?f30)"; formulae["f34"] = "?f32*cos(?f30)"; formulae["f35"] = "?f28+?f33"; formulae["f36"] = "?f29+?f34"; ListType handles; ComplexType handle; handle["draw:handle-position"] = "$0 10800"; handle["draw:handle-polar"] = "10800 10800"; handle["draw:handle-radius-range-minimum"] = "10800"; handle["draw:handle-radius-range-maximum"] = "10800"; handles.append(QVariant(handle)); handle.clear(); handle["draw:handle-position"] = "$1 $2"; handle["draw:handle-polar"] = "10800 10800"; handle["draw:handle-radius-range-minimum"] = '0'; handle["draw:handle-radius-range-maximum"] = "10800"; handles.append(QVariant(handle)); KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "circulararrow"; t.name = i18n("Circular Arrow"); t.family = "arrow"; t.toolTip = i18n("A circular-arrow"); t.iconName = koIconName("circular-arrow-shape"); KoProperties *properties = dataToProperties(modifiers, commands, handles, formulae); properties->setProperty(QLatin1String("viewBox"), QRect(0, 0, 21600, 21600)); t.properties = properties; addTemplate(t); } void EnhancedPathShapeFactory::addGearhead() { QStringList commands; commands.append("M 20 70"); commands.append("L 20 100 30 100 30 50 30 70 40 70 40 40 0 40 0 70 10 70 10 50 10 100 20 100"); commands.append("Z"); commands.append("N"); uint toothCount = 10; qreal toothAngle = 360.0 / qreal(toothCount); //kDebug() <<"toothAngle =" << toothAngle; qreal outerRadius = 0.5 * 25.0; qreal innerRadius = 0.5 * 17.0; QPointF center(20, 25); qreal radian = (270.0 - 0.35 * toothAngle) * M_PI / 180.0; commands.append(QString("M %1 %2").arg(center.x() + innerRadius * cos(radian)).arg(center.y() + innerRadius * sin(radian))); QString cmd("L"); for (uint i = 0; i < toothCount; ++i) { radian += 0.15 * toothAngle * M_PI / 180.0; cmd += QString(" %1 %2").arg(center.x() + outerRadius * cos(radian)).arg(center.y() + outerRadius * sin(radian)); radian += 0.35 * toothAngle * M_PI / 180.0; cmd += QString(" %1 %2").arg(center.x() + outerRadius * cos(radian)).arg(center.y() + outerRadius * sin(radian)); radian += 0.15 * toothAngle * M_PI / 180.0; cmd += QString(" %1 %2").arg(center.x() + innerRadius * cos(radian)).arg(center.y() + innerRadius * sin(radian)); radian += 0.35 * toothAngle * M_PI / 180.0; cmd += QString(" %1 %2").arg(center.x() + innerRadius * cos(radian)).arg(center.y() + innerRadius * sin(radian)); } //kDebug() <<"gear command =" << cmd; commands.append(cmd); commands.append("Z"); commands.append("N"); KoShapeTemplate t; t.id = KoPathShapeId; t.templateId = "gearhead"; t.name = i18n("Gearhead"); t.family = "funny"; t.toolTip = i18n("A gearhead"); t.iconName = koIconName("gearhead-shape"); KoProperties *properties = dataToProperties(QString(), commands, ListType(), ComplexType()); properties->setProperty("background", QVariant::fromValue(QColor(Qt::blue))); properties->setProperty(QLatin1String("viewBox"), QRect(0, 0, 40, 90)); t.properties = properties; addTemplate(t); } bool EnhancedPathShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const { Q_UNUSED(context); return (e.localName() == "custom-shape" && e.namespaceURI() == KoXmlNS::draw); } diff --git a/plugins/flake/textshape/TextPlugin.cpp b/plugins/flake/textshape/TextPlugin.cpp index 6dcaef25a2..74f632399d 100644 --- a/plugins/flake/textshape/TextPlugin.cpp +++ b/plugins/flake/textshape/TextPlugin.cpp @@ -1,51 +1,51 @@ /* This file is part of the KDE project * Copyright (C) 2006 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 "TextPlugin.h" #include "TextToolFactory.h" #include "ReferencesToolFactory.h" #include "ReviewToolFactory.h" #ifdef CREATE_TEXTDOCUMENT_INSPECTOR #include "TextDocumentInspectionPlugin.h" #endif #include "TextShapeFactory.h" #include "AnnotationTextShapeFactory.h" #include #include #include #include #ifdef CREATE_TEXTDOCUMENT_INSPECTOR K_PLUGIN_FACTORY_WITH_JSON(TextPluginFactory, "calligra_shape_text.json", registerPlugin(); registerPlugin(QLatin1String("TextDocumentInspection"));) #else K_PLUGIN_FACTORY_WITH_JSON(TextPluginFactory, "calligra_shape_text.json", registerPlugin();) #endif TextPlugin::TextPlugin(QObject *parent, const QVariantList &) : QObject(parent) { KoToolRegistry::instance()->add(new TextToolFactory()); - KoToolRegistry::instance()->add(new ReviewToolFactory()); - KoToolRegistry::instance()->add(new ReferencesToolFactory()); + //KoToolRegistry::instance()->add(new ReviewToolFactory()); + //KoToolRegistry::instance()->add(new ReferencesToolFactory()); KoShapeRegistry::instance()->add(new TextShapeFactory()); - KoShapeRegistry::instance()->add(new AnnotationTextShapeFactory()); + //KoShapeRegistry::instance()->add(new AnnotationTextShapeFactory()); } #include diff --git a/plugins/flake/textshape/kotext/KoSection.h b/plugins/flake/textshape/kotext/KoSection.h index 7d4e29e9ea..12dd9c2ded 100644 --- a/plugins/flake/textshape/kotext/KoSection.h +++ b/plugins/flake/textshape/kotext/KoSection.h @@ -1,135 +1,135 @@ /* * Copyright (c) 2011 Boudewijn Rempt * Copyright (c) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSECTION_H #define KOSECTION_H #include "kritatext_export.h" #include #include #include #include #include #include -class KoXmlElement; +#include class KoShapeSavingContext; class KoTextSharedLoadingData; class KoSectionEnd; class KoElementReference; class KoTextInlineRdf; class KoSectionPrivate; /** * Contains the information about the current text:section. * * The element has the following attributes: * *
    *
  • text:condition *
  • text:display *
  • text:name *
  • text:protected *
  • text:protection-key *
  • text:protection-key-digest-algorithm *
  • text:style-name *
  • xml:id *
* (odf spec v.12) */ class KRITATEXT_EXPORT KoSection { public: ~KoSection(); /// Returns section name QString name() const; /// Returns starting and ending position of section in QTextDocument QPair bounds() const; /// Returns section level. Root section has @c 0 level. int level() const; /** Returns inlineRdf associated with section * @return pointer to the KoTextInlineRdf for this section */ KoTextInlineRdf *inlineRdf() const; /** Sets KoTextInlineRdf for this section * @param inlineRdf pointer to KoTextInlineRdf to set */ void setInlineRdf(KoTextInlineRdf *inlineRdf); bool loadOdf(const KoXmlElement &element, KoTextSharedLoadingData *sharedData, bool stylesDotXml); void saveOdf(KoShapeSavingContext &context) const; protected: const QScopedPointer d_ptr; private: Q_DISABLE_COPY(KoSection) Q_DECLARE_PRIVATE(KoSection) explicit KoSection(const QTextCursor &cursor, const QString &name, KoSection *parent); /// Changes section's name to @param name void setName(const QString &name); /// Sets paired KoSectionsEnd for this section. void setSectionEnd(KoSectionEnd *sectionEnd); /** * Sets level of section in section tree. * Root sections have @c 0 level. */ void setLevel(int level); /// Returns a pointer to the parent of the section in tree. KoSection *parent() const; /// Returns a vector of pointers to the children of the section. QVector children() const; /** * Specifies if end bound of section should stay on place when inserting text. * Used by KoTextLoader on document loading. * @see QTextCursor::setKeepPositionOnInsert(bool) */ void setKeepEndBound(bool state); /** * Inserts @param section to position @param childIdx of children */ void insertChild(int childIdx, KoSection *section); /** * Removes child on position @param childIdx */ void removeChild(int childIdx); friend class KoSectionModel; friend class KoTextLoader; // accesses setKeepEndBound() function friend class KoSectionEnd; friend class TestKoTextEditor; // accesses setKeepEndBound() function }; Q_DECLARE_METATYPE(KoSection *) Q_DECLARE_METATYPE(QList) #endif // KOSECTION_H diff --git a/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp b/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp index 1a6658c4ad..de796e7423 100644 --- a/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp +++ b/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp @@ -1,739 +1,668 @@ /* This file is part of the KDE project * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2011 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 "KoChangeTracker.h" //Calligra includes #include "styles/KoCharacterStyle.h" #include "KoChangeTrackerElement.h" #include #include #include #include #include #include #include #include #include "KoFormatChangeInformation.h" #include //KDE includes #include "TextDebug.h" #include //Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoChangeTracker::Private { public: Private() : changeId(1), recordChanges(false), displayChanges(false), insertionBgColor(101,255,137), deletionBgColor(255,185,185), formatChangeBgColor(195,195,255), changeSaveFormat(UNKNOWN) { } ~Private() { } QMultiHash children; QMultiHash duplicateIds; QHash parents; QHash changes; QHash loadedChanges; QHash changeInformation; QList saveChanges; QList acceptedRejectedChanges; int changeId; bool recordChanges; bool displayChanges; QColor insertionBgColor, deletionBgColor, formatChangeBgColor; QString changeAuthorName; KoChangeTracker::ChangeSaveFormat changeSaveFormat; }; KoChangeTracker::KoChangeTracker(QObject *parent) : QObject(parent), d(new Private()) { d->changeId = 1; } KoChangeTracker::~KoChangeTracker() { delete d; } void KoChangeTracker::setRecordChanges(bool enabled) { d->recordChanges = enabled; } bool KoChangeTracker::recordChanges() const { return d->recordChanges; } void KoChangeTracker::setDisplayChanges(bool enabled) { d->displayChanges = enabled; } bool KoChangeTracker::displayChanges() const { return d->displayChanges; } QString KoChangeTracker::authorName() const { return d->changeAuthorName; } void KoChangeTracker::setAuthorName(const QString &authorName) { d->changeAuthorName = authorName; } KoChangeTracker::ChangeSaveFormat KoChangeTracker::saveFormat() const { return d->changeSaveFormat; } void KoChangeTracker::setSaveFormat(ChangeSaveFormat saveFormat) { d->changeSaveFormat = saveFormat; } int KoChangeTracker::getFormatChangeId(const KUndo2MagicString &title, const QTextFormat &format, const QTextFormat &prevFormat, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::FormatChange); changeElement->setChangeFormat(format); changeElement->setPrevFormat(prevFormat); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } int KoChangeTracker::getInsertChangeId(const KUndo2MagicString &title, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::InsertChange); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } int KoChangeTracker::getDeleteChangeId(const KUndo2MagicString &title, const QTextDocumentFragment &selection, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::DeleteChange); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setDeleteData(selection); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } KoChangeTrackerElement* KoChangeTracker::elementById(int id) const { if (isDuplicateChangeId(id)) { id = originalChangeId(id); } return d->changes.value(id); } bool KoChangeTracker::removeById(int id, bool freeMemory) { if (freeMemory) { KoChangeTrackerElement *temp = d->changes.value(id); delete temp; } return d->changes.remove(id); } bool KoChangeTracker::containsInlineChanges(const QTextFormat &format) const { if (format.property(KoCharacterStyle::ChangeTrackerId).toInt()) return true; return false; } int KoChangeTracker::mergeableId(KoGenChange::Type type, const KUndo2MagicString &title, int existingId) const { if (!existingId || !d->changes.value(existingId)) return 0; if (d->changes.value(existingId)->getChangeType() == type && d->changes.value(existingId)->getChangeTitle() == title) { return existingId; } else { if (d->parents.contains(existingId)) { return mergeableId(type, title, d->parents.value(existingId)); } else { return 0; } } } int KoChangeTracker::split(int changeId) { KoChangeTrackerElement *element = new KoChangeTrackerElement(*d->changes.value(changeId)); d->changes.insert(d->changeId, element); return d->changeId++; } bool KoChangeTracker::isParent(int testedParentId, int testedChildId) const { if ((testedParentId == testedChildId) && !d->acceptedRejectedChanges.contains(testedParentId)) return true; else if (d->parents.contains(testedChildId)) return isParent(testedParentId, d->parents.value(testedChildId)); else return false; } void KoChangeTracker::setParent(int child, int parent) { if (!d->children.values(parent).contains(child)) { d->children.insert(parent, child); } if (!d->parents.contains(child)) { d->parents.insert(child, parent); } } int KoChangeTracker::parent(int changeId) const { if (!d->parents.contains(changeId)) return 0; if (d->acceptedRejectedChanges.contains(d->parents.value(changeId))) return parent(d->parents.value(changeId)); return d->parents.value(changeId); } int KoChangeTracker::createDuplicateChangeId(int existingChangeId) { int duplicateChangeId = d->changeId; d->changeId++; d->duplicateIds.insert(existingChangeId, duplicateChangeId); return duplicateChangeId; } bool KoChangeTracker::isDuplicateChangeId(int duplicateChangeId) const { return d->duplicateIds.values().contains(duplicateChangeId); } int KoChangeTracker::originalChangeId(int duplicateChangeId) const { int originalChangeId = 0; QMultiHash::const_iterator i = d->duplicateIds.constBegin(); while (i != d->duplicateIds.constEnd()) { if (duplicateChangeId == i.value()) { originalChangeId = i.key(); break; } ++i; } return originalChangeId; } void KoChangeTracker::acceptRejectChange(int changeId, bool set) { if (set) { if (!d->acceptedRejectedChanges.contains(changeId)) d->acceptedRejectedChanges.append(changeId); } else { if (d->acceptedRejectedChanges.contains(changeId)) d->acceptedRejectedChanges.removeAll(changeId); } d->changes.value(changeId)->setAcceptedRejected(set); } bool KoChangeTracker::saveInlineChange(int changeId, KoGenChange &change) { if (!d->changes.contains(changeId)) return false; change.setType(d->changes.value(changeId)->getChangeType()); change.addChangeMetaData("dc-creator", d->changes.value(changeId)->getCreator()); change.addChangeMetaData("dc-date", d->changes.value(changeId)->getDate()); if (d->changes.value(changeId)->hasExtraMetaData()) change.addChildElement("changeMetaData", d->changes.value(changeId)->getExtraMetaData()); return true; } QMap KoChangeTracker::saveInlineChanges(QMap changeTransTable, KoGenChanges &genChanges) { foreach (int changeId, d->changes.keys()) { // return if the id we find in the changetranstable already has a length. if (changeTransTable.value(changeId).length()) { continue; } if ((elementById(changeId)->getChangeType() == KoGenChange::DeleteChange) && (saveFormat() == KoChangeTracker::ODF_1_2)) { continue; } KoGenChange change; if (saveFormat() == KoChangeTracker::ODF_1_2) { change.setChangeFormat(KoGenChange::ODF_1_2); } else { change.setChangeFormat(KoGenChange::DELTAXML); } saveInlineChange(changeId, change); QString changeName = genChanges.insert(change); changeTransTable.insert(changeId, changeName); } return changeTransTable; } void KoChangeTracker::setFormatChangeInformation(int formatChangeId, KoFormatChangeInformation *formatInformation) { d->changeInformation.insert(formatChangeId, formatInformation); } KoFormatChangeInformation *KoChangeTracker::formatChangeInformation(int formatChangeId) const { return d->changeInformation.value(formatChangeId); } -void KoChangeTracker::loadOdfChanges(const KoXmlElement& element) -{ - if (element.namespaceURI() == KoXmlNS::text) { - KoXmlElement tag; - forEachElement(tag, element) { - if (! tag.isNull()) { - const QString localName = tag.localName(); - if (localName == "changed-region") { - KoChangeTrackerElement *changeElement = 0; - KoXmlElement region; - forEachElement(region, tag) { - if (!region.isNull()) { - if (region.localName() == "insertion") { - changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::InsertChange); - } else if (region.localName() == "format-change") { - changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::FormatChange); - } else if (region.localName() == "deletion") { - changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::DeleteChange); - } - KoXmlElement metadata = region.namedItemNS(KoXmlNS::office,"change-info").toElement(); - if (!metadata.isNull()) { - KoXmlElement date = metadata.namedItem("dc:date").toElement(); - if (!date.isNull()) { - changeElement->setDate(date.text()); - } - KoXmlElement creator = metadata.namedItem("dc:creator").toElement(); - if (!date.isNull()) { - changeElement->setCreator(creator.text()); - } - //TODO load comments -/* KoXmlElement extra = metadata.namedItem("dc-").toElement(); - if (!date.isNull()) { - debugText << "creator: " << creator.text(); - changeElement->setCreator(creator.text()); - }*/ - } - changeElement->setEnabled(d->recordChanges); - d->changes.insert( d->changeId, changeElement); - d->loadedChanges.insert(tag.attributeNS(KoXmlNS::text,"id"), d->changeId++); - } - } - } - } - } - } else { - //This is the ODF 1.2 Change Format - KoXmlElement tag; - forEachElement(tag, element) { - if (! tag.isNull()) { - const QString localName = tag.localName(); - if (localName == "change-transaction") { - KoChangeTrackerElement *changeElement = 0; - //Set the change element as an insertion element for now - //Will be changed to the correct type when actual changes referencing this change-id are encountered - changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::delta,"change-id")),KoGenChange::InsertChange); - KoXmlElement metadata = tag.namedItemNS(KoXmlNS::delta,"change-info").toElement(); - if (!metadata.isNull()) { - KoXmlElement date = metadata.namedItem("dc:date").toElement(); - if (!date.isNull()) { - changeElement->setDate(date.text()); - } - KoXmlElement creator = metadata.namedItem("dc:creator").toElement(); - if (!creator.isNull()) { - changeElement->setCreator(creator.text()); - } - } - changeElement->setEnabled(d->recordChanges); - d->changes.insert( d->changeId, changeElement); - d->loadedChanges.insert(tag.attributeNS(KoXmlNS::delta,"change-id"), d->changeId++); - } - } - } - } +void KoChangeTracker::loadOdfChanges(const KoXmlElement& ) +{ } int KoChangeTracker::getLoadedChangeId(const QString &odfId) const { return d->loadedChanges.value(odfId); } int KoChangeTracker::getDeletedChanges(QVector& deleteVector) const { int numAppendedItems = 0; foreach (KoChangeTrackerElement *element, d->changes.values()) { if(element->getChangeType() == KoGenChange::DeleteChange && !element->acceptedRejected()) { deleteVector << element; numAppendedItems++; } } return numAppendedItems; } QColor KoChangeTracker::getInsertionBgColor() const { return d->insertionBgColor; } QColor KoChangeTracker::getDeletionBgColor() const { return d->deletionBgColor; } QColor KoChangeTracker::getFormatChangeBgColor() const { return d->formatChangeBgColor; } void KoChangeTracker::setInsertionBgColor(const QColor& bgColor) { d->insertionBgColor = bgColor; } void KoChangeTracker::setDeletionBgColor(const QColor& bgColor) { d->deletionBgColor = bgColor; } void KoChangeTracker::setFormatChangeBgColor(const QColor& bgColor) { d->formatChangeBgColor = bgColor; } ////A convenience function to get a ListIdType from a format //static KoListStyle::ListIdType ListId(const QTextListFormat &format) //{ // KoListStyle::ListIdType listId; // if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) { // listId = format.property(KoListStyle::ListId).toUInt(); // } // else { // listId = format.property(KoListStyle::ListId).toULongLong(); // } // return listId; //} QTextDocumentFragment KoChangeTracker::generateDeleteFragment(const QTextCursor &cursor) { QTextCursor editCursor(cursor); QTextDocument *document = cursor.document(); QTextDocument deletedDocument; QTextDocument deleteCursor(&deletedDocument); KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); if (textObjectManager) { for (int i = cursor.anchor();i <= cursor.position(); i++) { if (document->characterAt(i) == QChar::ObjectReplacementCharacter) { editCursor.setPosition(i+1); - } + } } } QTextBlock currentBlock = document->findBlock(cursor.anchor()); QTextBlock startBlock = currentBlock; QTextBlock endBlock = document->findBlock(cursor.position()).next(); currentBlock = document->findBlock(cursor.anchor()); startBlock = currentBlock; endBlock = document->findBlock(cursor.position()).next(); for (;currentBlock != endBlock; currentBlock = currentBlock.next()) { editCursor.setPosition(currentBlock.position()); if (editCursor.currentTable()) { QTextTableFormat tableFormat = editCursor.currentTable()->format(); editCursor.currentTable()->setFormat(tableFormat); } if (currentBlock != startBlock) { QTextBlockFormat blockFormat; editCursor.mergeBlockFormat(blockFormat); } } return cursor.selection(); } bool KoChangeTracker::checkListDeletion(const QTextList &list, const QTextCursor &cursor) { int startOfList = (list.item(0).position() - 1); int endOfList = list.item(list.count() -1).position() + list.item(list.count() -1).length() - 1; if ((cursor.anchor() <= startOfList) && (cursor.position() >= endOfList)) return true; else { /***************************************************************************************************/ /* Qt Quirk Work-Around */ /***************************************************************************************************/ if ((cursor.anchor() == (startOfList + 1)) && (cursor.position() > endOfList)) { return true; /***************************************************************************************************/ } else if((cursor.anchor() <= startOfList) && (list.count() == 1)) { return true; } else { return false; } } } void KoChangeTracker::insertDeleteFragment(QTextCursor &cursor) { QTextDocument tempDoc; QTextCursor tempCursor(&tempDoc); bool deletedListItem = false; for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) { //This condition is for the work-around for a Qt behaviour //Even if a delete ends at the end of a table, the fragment will have an empty block after the table //If such a block is detected then, just ignore it if ((currentBlock.next() == tempDoc.end()) && (currentBlock.text().length() == 0) && (QTextCursor(currentBlock.previous()).currentTable())) { continue; } tempCursor.setPosition(currentBlock.position()); QTextList *textList = tempCursor.currentList(); int outlineLevel = currentBlock.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); KoList *currentList = KoTextDocument(cursor.document()).list(cursor.block()); int docOutlineLevel = cursor.block().blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); if (docOutlineLevel) { //Even though we got a list, it is actually a list for storing headings. So don't consider it currentList = 0; } QTextList *previousTextList = currentBlock.previous().isValid() ? QTextCursor(currentBlock.previous()).currentList():0; if (textList && previousTextList && (textList != previousTextList) && (KoList::level(currentBlock) == KoList::level(currentBlock.previous()))) { //Even though we are already in a list, the QTextList* of the current block is differnt from that of the previous block //Also the levels of the list-items ( previous and current ) are the same. //This can happen only when two lists are merged together without any intermediate content. //So we need to create a new list. currentList = 0; } if (textList) { if (deletedListItem && currentBlock != tempDoc.begin()) { // Found a deleted list item in the fragment. So insert a new list-item int deletedListItemLevel = KoList::level(currentBlock); if (!(QTextCursor(currentBlock.previous()).currentTable())) { cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat()); } else { cursor.mergeBlockFormat(currentBlock.blockFormat()); } if(!currentList) { if (!outlineLevel) { //This happens when a part of a paragraph and a succeeding list-item are deleted together //So go to the next block and insert it in the list there. QTextCursor tmp(cursor); tmp.setPosition(tmp.block().next().position()); currentList = KoTextDocument(tmp.document()).list(tmp.block()); } else { // This is a heading. So find the KoList for heading and add the block there KoList *headingList = KoTextDocument(cursor.document()).headingList(); currentList = headingList; } } currentList->add(cursor.block(), deletedListItemLevel); } } else if (tempCursor.currentTable()) { QTextTable *deletedTable = tempCursor.currentTable(); int numRows = deletedTable->rows(); int numColumns = deletedTable->columns(); QTextTable *insertedTable = cursor.insertTable(numRows, numColumns, deletedTable->format()); for (int i=0; icellAt(i,j).firstCursorPosition().position()); tempCursor.setPosition(deletedTable->cellAt(i,j).lastCursorPosition().position(), QTextCursor::KeepAnchor); insertedTable->cellAt(i,j).setFormat(deletedTable->cellAt(i,j).format().toTableCellFormat()); cursor.setPosition(insertedTable->cellAt(i,j).firstCursorPosition().position()); cursor.insertFragment(tempCursor.selection()); } } tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position()); currentBlock = tempCursor.block(); //Move the cursor outside of table cursor.setPosition(cursor.position() + 1); continue; } else { // This block does not contain a list. So no special work here. if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) { cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat()); } if (QTextCursor(currentBlock.previous()).currentTable()) { cursor.mergeBlockFormat(currentBlock.blockFormat()); } } /********************************************************************************************************************/ /*This section of code is a work-around for a bug in the Qt. This work-around is safe. If and when the bug is fixed */ /*the if condition would never be true and the code would never get executed */ /********************************************************************************************************************/ if ((KoList::level(cursor.block()) != KoList::level(currentBlock)) && currentBlock.text().length()) { if (!currentList) { QTextCursor tmp(cursor); tmp.setPosition(tmp.block().previous().position()); currentList = KoTextDocument(tmp.document()).list(tmp.block()); } currentList->add(cursor.block(), KoList::level(currentBlock)); } /********************************************************************************************************************/ // Finally insert all the contents of the block into the main document. QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { cursor.insertText(currentFragment.text(), currentFragment.charFormat()); } } } } int KoChangeTracker::fragmentLength(const QTextDocumentFragment &fragment) { QTextDocument tempDoc; QTextCursor tempCursor(&tempDoc); tempCursor.insertFragment(fragment); int length = 0; bool deletedListItem = false; for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); if (tempCursor.currentList()) { if (currentBlock != tempDoc.begin() && deletedListItem) length += 1; //For the Block separator } else if (tempCursor.currentTable()) { QTextTable *deletedTable = tempCursor.currentTable(); int numRows = deletedTable->rows(); int numColumns = deletedTable->columns(); for (int i=0; icellAt(i,j).lastCursorPosition().position() - deletedTable->cellAt(i,j).firstCursorPosition().position()); } } tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position()); currentBlock = tempCursor.block(); length += 1; continue; } else { if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) length += 1; //For the Block Separator } QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) length += currentFragment.text().length(); } } return length; } diff --git a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp index 0f4e92a38a..8412988323 100644 --- a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp +++ b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp @@ -1,1143 +1,1116 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextWriter_p.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 "TextDebug.h" #include #include // A convenience function to get a listId from a list-format static KoListStyle::ListIdType ListId(const QTextListFormat &format) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = format.property(KoListStyle::ListId).toUInt(); else listId = format.property(KoListStyle::ListId).toULongLong(); return listId; } typedef QPair Attribute; KoTextWriter::Private::Private(KoShapeSavingContext &context) : rdfData(0) , sharedData(0) , styleManager(0) , document(0) , writer(0) , context(context) { currentPairedInlineObjectsStack = new QStack(); writer = &context.xmlWriter(); } void KoTextWriter::Private::writeBlocks(QTextDocument *document, int from, int to, QHash &listStyles, QTextTable *currentTable, QTextList *currentList) { pairedInlineObjectsStackStack.push(currentPairedInlineObjectsStack); currentPairedInlineObjectsStack = new QStack(); QTextBlock block = document->findBlock(from); // Here we are going to detect all sections that // are positioned entirely inside selection. // They will stay untouched, and others will be omitted. // So we are using stack to detect them, by going through // the selection and finding open/close pairs. QSet entireWithinSectionNames; QStack sectionNamesStack; QTextCursor cur(document); cur.setPosition(from); while (to == -1 || cur.position() <= to) { if (cur.block().position() >= from) { // Begin of the block is inside selection. foreach (const KoSection *sec, KoSectionUtils::sectionStartings(cur.blockFormat())) { sectionNamesStack.push_back(sec->name()); } } if (to == -1 || cur.block().position() + cur.block().length() - 1 <= to) { // End of the block is inside selection. foreach (const KoSectionEnd *sec, KoSectionUtils::sectionEndings(cur.blockFormat())) { if (!sectionNamesStack.empty() && sectionNamesStack.top() == sec->name()) { sectionNamesStack.pop(); entireWithinSectionNames.insert(sec->name()); } } } if (!KoSectionUtils::getNextBlock(cur)) { break; } } while (block.isValid() && ((to == -1) || (block.position() <= to))) { QTextCursor cursor(block); int frameType = cursor.currentFrame()->format().intProperty(KoText::SubFrameType); if (frameType == KoText::AuxillaryFrameType) { break; // we've reached the "end" (end/footnotes saved by themselves) // note how NoteFrameType passes through here so the notes can // call writeBlocks to save their contents. } QTextBlockFormat format = block.blockFormat(); foreach (const KoSection *section, KoSectionUtils::sectionStartings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(section->name())) { section->saveOdf(context); } } if (format.hasProperty(KoParagraphStyle::HiddenByTable)) { block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::TableOfContentsData)) { saveTableOfContents(document, listStyles, block); block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::BibliographyData)) { saveBibliography(document, listStyles, block); block = block.next(); continue; } if (cursor.currentTable() && cursor.currentTable() != currentTable) { // Call the code to save the table.... saveTable(cursor.currentTable(), listStyles, from, to); // We skip to the end of the table. block = cursor.currentTable()->lastCursorPosition().block(); block = block.next(); continue; } if (cursor.currentList() && cursor.currentList() != currentList) { int previousBlockNumber = block.blockNumber(); block = saveList(block, listStyles, 1, currentTable); int blockNumberToProcess = block.blockNumber(); if (blockNumberToProcess != previousBlockNumber) continue; } saveParagraph(block, from, to); foreach (const KoSectionEnd *sectionEnd, KoSectionUtils::sectionEndings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(sectionEnd->name())) { sectionEnd->saveOdf(context); } } block = block.next(); } // while Q_ASSERT(!pairedInlineObjectsStackStack.isEmpty()); delete currentPairedInlineObjectsStack; currentPairedInlineObjectsStack = pairedInlineObjectsStackStack.pop(); } QHash KoTextWriter::Private::saveListStyles(QTextBlock block, int to) { QHash generatedLists; QHash listStyles; for (;block.isValid() && ((to == -1) || (block.position() < to)); block = block.next()) { QTextList *textList = block.textList(); if (!textList) continue; KoListStyle::ListIdType listId = ListId(textList->format()); if (KoList *list = KoTextDocument(document).list(listId)) { if (generatedLists.contains(list)) { if (!listStyles.contains(textList)) listStyles.insert(textList, generatedLists.value(list)); continue; } KoListStyle *listStyle = list->style(); if (!listStyle || listStyle->isOulineStyle()) continue; bool automatic = listStyle->styleId() == 0; KoGenStyle style(automatic ? KoGenStyle::ListAutoStyle : KoGenStyle::ListStyle); if (automatic && context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); listStyle->saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle->name(), listStyle->isNumberingStyle() ? KoGenStyles::AllowDuplicates : KoGenStyles::DontAddNumberToName); listStyles[textList] = generatedName; generatedLists.insert(list, generatedName); } else { if (listStyles.contains(textList)) continue; KoListLevelProperties llp = KoListLevelProperties::fromTextList(textList); KoGenStyle style(KoGenStyle::ListAutoStyle); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoListStyle listStyle; listStyle.setLevelProperties(llp); if (listStyle.isOulineStyle()) { continue; } listStyle.saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle.name()); listStyles[textList] = generatedName; } } return listStyles; } //---------------------------- PRIVATE ----------------------------------------------------------- void KoTextWriter::Private::openTagRegion(ElementType elementType, TagInformation& tagInformation) { //debugText << "tag:" << tagInformation.name() << openedTagStack.size(); if (tagInformation.name()) { writer->startElement(tagInformation.name(), elementType != ParagraphOrHeader); foreach (const Attribute &attribute, tagInformation.attributes()) { writer->addAttribute(attribute.first.toLocal8Bit(), attribute.second); } } openedTagStack.push(tagInformation.name()); //debugText << "stack" << openedTagStack.size(); } void KoTextWriter::Private::closeTagRegion() { // the tag needs to be closed even if there is no change tracking //debugText << "stack" << openedTagStack.size(); const char *tagName = openedTagStack.pop(); //debugText << "tag:" << tagName << openedTagStack.size(); if (tagName) { writer->endElement(); // close the tag } } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlock &block) { return KoTextWriter::saveParagraphStyle(block, styleManager, context); } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &charFormat) { return KoTextWriter::saveParagraphStyle(blockFormat, charFormat, styleManager, context); } QString KoTextWriter::Private::saveCharacterStyle(const QTextCharFormat &charFormat, const QTextCharFormat &blockCharFormat) { KoCharacterStyle *defaultCharStyle = styleManager->defaultCharacterStyle(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(charFormat.intProperty(KoCharacterStyle::StyleId)); if (!originalCharStyle) originalCharStyle = defaultCharStyle; QString generatedName; QString displayName = originalCharStyle->name(); QString internalName = QString(QUrl::toPercentEncoding(displayName, "", " ")).replace('%', '_'); KoCharacterStyle *autoStyle = originalCharStyle->autoStyle(charFormat, blockCharFormat); if (autoStyle->isEmpty()) { // This is the real, unmodified character style. if (originalCharStyle != defaultCharStyle) { KoGenStyle style(KoGenStyle::TextStyle, "text"); originalCharStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TextAutoStyle, "text", originalCharStyle != defaultCharStyle ? internalName : "" /*parent*/); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); autoStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, "T"); } delete autoStyle; return generatedName; } QString KoTextWriter::Private::saveTableStyle(const QTextTable& table) { KoTableStyle *originalTableStyle = styleManager->tableStyle(table.format().intProperty(KoTableStyle::StyleId)); QString generatedName; QString internalName; if (originalTableStyle) { internalName = QString(QUrl::toPercentEncoding(originalTableStyle->name(), "", " ")).replace('%', '_'); } KoTableStyle tableStyle(table.format()); if ((originalTableStyle) && (*originalTableStyle == tableStyle)) { // This is the real unmodified table style KoGenStyle style(KoGenStyle::TableStyle, "table"); originalTableStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TableAutoStyle, "table", internalName); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); if (originalTableStyle) tableStyle.removeDuplicates(*originalTableStyle); if (!tableStyle.isEmpty()) { tableStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, "Table"); } } return generatedName; } QString KoTextWriter::Private::saveTableColumnStyle(const KoTableColumnStyle& tableColumnStyle, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableColumnAutoStyle, "table-column"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableColumnStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableRowStyle(const KoTableRowStyle& tableRowStyle, int rowNumber, const QString& tableStyleName) { QString generatedName = tableStyleName + '.' + QString::number(rowNumber + 1); KoGenStyle style(KoGenStyle::TableRowAutoStyle, "table-row"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableRowStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableCellStyle(const QTextTableCellFormat& cellFormat, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableCellAutoStyle, "table-cell"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoTableCellStyle cellStyle(cellFormat); cellStyle.saveOdf(style, context); generatedName = context.mainStyles().insert(style, generatedName); return generatedName; } -void KoTextWriter::Private::saveInlineRdf(KoTextInlineRdf* rdf, TagInformation* tagInfos) +void KoTextWriter::Private::saveInlineRdf(KoTextInlineRdf* , TagInformation* ) { - QBuffer rdfXmlData; - KoXmlWriter rdfXmlWriter(&rdfXmlData); - rdfXmlWriter.startDocument("rdf"); - rdfXmlWriter.startElement("rdf"); - rdf->saveOdf(context, &rdfXmlWriter); - rdfXmlWriter.endElement(); - rdfXmlWriter.endDocument(); - - KoXmlDocument xmlReader; - xmlReader.setContent(rdfXmlData.data(), true); - KoXmlElement mainElement = xmlReader.documentElement(); - foreach (const Attribute &attributeNameNS, mainElement.attributeFullNames()) { - QString attributeName = QString("%1:%2").arg(KoXmlNS::nsURI2NS(attributeNameNS.first)) - .arg(attributeNameNS.second); - if (attributeName.startsWith(':')) - attributeName.prepend("xml"); - tagInfos->addAttribute(attributeName, mainElement.attribute(attributeNameNS.second)); - } } /* Note on saving textranges: Start and end tags of textranges can appear on cursor positions in a text block. in front of the first text element, between the elements, or behind the last. A textblock is composed of no, one or many text fragments. If there is no fragment at all, the only possible cursor position is 0 (relative to the begin of the block). Example: ([] marks a block, {} a fragment) Three blocks, first with text fragments {AB} {C}, second empty, last with {DEF}. Possible positions are: [|{A|B}|{C}|] [|] [|{D|E|F}|] Start tags are ideally written in front of the content they are tagging, not behind the previous content. That way tags which are at the very begin of the complete document do not need special handling. End tags are ideally written directly behind the content, and not in front of the next content. That way end tags which are at the end of the complete document do not need special handling. Next there is the case of start tags which are at the final position of a text block: the content they belong to includes the block end/border, so they need to be written at the place of the last position. Then there is the case of end tags at the first position of a text block: the content they belong to includes the block start/border, so they need to be written at the place of the first position. Example: (< marks a start tag, > marks an end tag) [|>{}|{}|<] [|><] [|>{||}|<] */ void KoTextWriter::Private::saveParagraph(const QTextBlock &block, int from, int to) { QTextCursor cursor(block); QTextBlockFormat blockFormat = block.blockFormat(); const int outlineLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); TagInformation blockTagInformation; if (outlineLevel > 0) { blockTagInformation.setTagName("text:h"); blockTagInformation.addAttribute("text:outline-level", outlineLevel); if (blockFormat.boolProperty(KoParagraphStyle::IsListHeader) || blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem)) { blockTagInformation.addAttribute("text:is-list-header", "true"); } } else { blockTagInformation.setTagName("text:p"); } openTagRegion(KoTextWriter::Private::ParagraphOrHeader, blockTagInformation); QString styleName = saveParagraphStyle(block); if (!styleName.isEmpty()) writer->addAttribute("text:style-name", styleName); KoElementReference xmlid; xmlid.invalidate(); QTextBlock currentBlock = block; KoTextBlockData blockData(currentBlock); if (blockData.saveXmlID()) { xmlid = context.xmlid(&blockData); xmlid.saveOdf(writer, KoElementReference::TextId); } // Write the fragments and their formats QTextCharFormat blockCharFormat = cursor.blockCharFormat(); QTextCharFormat previousCharFormat; QTextBlock::iterator it; if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(blockCharFormat)) { // Write xml:id here for Rdf debugText << "have inline rdf xmlid:" << inlineRdf->xmlId() << "active xml id" << xmlid.toString(); inlineRdf->saveOdf(context, writer, xmlid); } const KoTextRangeManager *textRangeManager = KoTextDocument(block.document()).textRangeManager(); if (textRangeManager) { // write tags for ranges which end at the first position of the block const QHash endingTextRangesAtStart = textRangeManager->textRangesChangingWithin(block.document(), block.position(), block.position(), globalFrom, globalTo); foreach (const KoTextRange *range, endingTextRangesAtStart) { range->saveOdf(context, block.position(), KoTextRange::EndTag); } } QString previousFragmentLink; // stores the end position of the last fragment, is position of the block without any fragment at all int lastEndPosition = block.position(); for (it = block.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); const int fragmentStart = currentFragment.position(); const int fragmentEnd = fragmentStart + currentFragment.length(); if (to != -1 && fragmentStart >= to) break; if (currentFragment.isValid()) { QTextCharFormat charFormat = currentFragment.charFormat(); if ((!previousFragmentLink.isEmpty()) && (charFormat.anchorHref() != previousFragmentLink || !charFormat.isAnchor())) { // Close the current text:a closeTagRegion(); previousFragmentLink.clear(); } if (charFormat.isAnchor() && charFormat.anchorHref() != previousFragmentLink) { // Open a text:a previousFragmentLink = charFormat.anchorHref(); TagInformation linkTagInformation; if (charFormat.intProperty(KoCharacterStyle::AnchorType) == KoCharacterStyle::Bookmark) { linkTagInformation.setTagName("text:bookmark-ref"); QString href = previousFragmentLink.right(previousFragmentLink.size()-1); linkTagInformation.addAttribute("text:ref-name", href); //linkTagInformation.addAttribute("text:ref-format", add the style of the ref here); } else { linkTagInformation.setTagName("text:a"); linkTagInformation.addAttribute("xlink:type", "simple"); linkTagInformation.addAttribute("xlink:href", charFormat.anchorHref()); } if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(charFormat)) { // Write xml:id here for Rdf debugText << "have inline rdf xmlid:" << inlineRdf->xmlId(); saveInlineRdf(inlineRdf, &linkTagInformation); } openTagRegion(KoTextWriter::Private::Span, linkTagInformation); } KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); KoInlineObject *inlineObject = textObjectManager ? textObjectManager->inlineTextObject(charFormat) : 0; // If we are in an inline object if (currentFragment.length() == 1 && inlineObject && currentFragment.text()[0].unicode() == QChar::ObjectReplacementCharacter) { bool saveInlineObject = true; if (KoTextMeta* z = dynamic_cast(inlineObject)) { if (z->position() < from) { // // This starts before the selection, default // to not saving it with special cases to allow saving // saveInlineObject = false; if (z->type() == KoTextMeta::StartBookmark) { if (z->endBookmark()->position() > from) { // // They have selected something starting after the // opening but before the // saveInlineObject = true; } } } } // get all text ranges which start before this inline object // or end directly after it (+1 to last position for that) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), currentFragment.position(), currentFragment.position()+1, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // get all text ranges which start before this const QList textRangesBefore = textRanges.values(currentFragment.position()); // write tags for ranges which start before this content or at positioned at it foreach (const KoTextRange *range, textRangesBefore) { range->saveOdf(context, currentFragment.position(), KoTextRange::StartTag); } bool saveSpan = dynamic_cast(inlineObject) != 0; if (saveSpan) { QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); } else { saveSpan = false; } } if (saveInlineObject) { inlineObject->saveOdf(context); } if (saveSpan) { writer->endElement(); } // write tags for ranges which end after this inline object const QList textRangesAfter = textRanges.values(currentFragment.position()+1); foreach (const KoTextRange *range, textRangesAfter) { range->saveOdf(context, currentFragment.position()+1, KoTextRange::EndTag); } // // Track the end marker for matched pairs so we produce valid // ODF // if (KoTextMeta* z = dynamic_cast(inlineObject)) { debugText << "found kometa, type:" << z->type(); if (z->type() == KoTextMeta::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoTextMeta::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }/* else if (KoBookmark* z = dynamic_cast(inlineObject)) { if (z->type() == KoBookmark::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoBookmark::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }*/ } else { // Normal block, easier to handle QString styleName = saveCharacterStyle(charFormat, blockCharFormat); TagInformation fragmentTagInformation; if (!styleName.isEmpty() /*&& !identical*/) { fragmentTagInformation.setTagName("text:span"); fragmentTagInformation.addAttribute("text:style-name", styleName); } openTagRegion(KoTextWriter::Private::Span, fragmentTagInformation); QString text = currentFragment.text(); int spanFrom = fragmentStart >= from ? fragmentStart : from; int spanTo = to == -1 ? fragmentEnd : (fragmentEnd > to ? to : fragmentEnd); // get all text ranges which change within this span // or end directly after it (+1 to last position to include those) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), spanFrom, spanTo, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // avoid mid, if possible if (spanFrom != fragmentStart || spanTo != fragmentEnd || !textRanges.isEmpty()) { if (textRanges.isEmpty()) { writer->addTextSpan(text.mid(spanFrom - fragmentStart, spanTo - spanFrom)); } else { // split the fragment into subspans at the points of range starts/ends QList subSpanTos = textRanges.uniqueKeys(); qSort(subSpanTos); // ensure last subSpanTo to be at the end if (subSpanTos.last() != spanTo) { subSpanTos.append(spanTo); } // spanFrom should not need to be included if (subSpanTos.first() == spanFrom) { subSpanTos.removeOne(spanFrom); } int subSpanFrom = spanFrom; // for all subspans foreach (int subSpanTo, subSpanTos) { // write tags for text ranges which start before this subspan or are positioned at it const QList textRangesStartingBefore = textRanges.values(subSpanFrom); foreach (const KoTextRange *range, textRangesStartingBefore) { range->saveOdf(context, subSpanFrom, KoTextRange::StartTag); } // write subspan content writer->addTextSpan(text.mid(subSpanFrom - fragmentStart, subSpanTo - subSpanFrom)); // write tags for text ranges which end behind this subspan const QList textRangesEndingBehind = textRanges.values(subSpanTo); foreach (const KoTextRange *range, textRangesEndingBehind) { range->saveOdf(context, subSpanTo, KoTextRange::EndTag); } subSpanFrom = subSpanTo; } } } else { writer->addTextSpan(text); } closeTagRegion(); } // if (inlineObject) previousCharFormat = charFormat; lastEndPosition = fragmentEnd; } } if (!previousFragmentLink.isEmpty()) { writer->endElement(); } if (it.atEnd() && textRangeManager && ((to == -1) || (lastEndPosition <= to))) { // write tags for ranges which start at the last position of the block, // i.e. at the position after the last (text) fragment const QHash startingTextRangesAtEnd = textRangeManager->textRangesChangingWithin(block.document(), lastEndPosition, lastEndPosition, globalFrom, globalTo); foreach (const KoTextRange *range, startingTextRangesAtEnd) { range->saveOdf(context, lastEndPosition, KoTextRange::StartTag); } } QString text = block.text(); if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) { if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); QSharedPointer endCharStyle = v.value< QSharedPointer >(); if (!endCharStyle.isNull()) { QTextCharFormat charFormat; endCharStyle->applyStyle(charFormat); QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); writer->endElement(); } } } } if (to !=-1 && to < block.position() + block.length()) { foreach (KoInlineObject* inlineObject, *currentPairedInlineObjectsStack) { inlineObject->saveOdf(context); } } closeTagRegion(); } void KoTextWriter::Private::saveTable(QTextTable *table, QHash &listStyles, int from, int to) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(table); int numberHeadingRows = table->format().property(KoTableStyle::NumberHeadingRows).toInt(); TagInformation tableTagInformation; QString tableStyleName = saveTableStyle(*table); tableTagInformation.setTagName("table:table"); tableTagInformation.addAttribute("table:style-name", tableStyleName); if (table->format().boolProperty(KoTableStyle::TableIsProtected)) { tableTagInformation.addAttribute("table:protected", "true"); } if (table->format().hasProperty(KoTableStyle::TableTemplate)) { tableTagInformation.addAttribute("table:template-name", sharedData->styleName(table->format().intProperty(KoTableStyle::TableTemplate))); } if (table->format().boolProperty(KoTableStyle::UseBandingColumnStyles)) { tableTagInformation.addAttribute("table:use-banding-columns-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseBandingRowStyles)) { tableTagInformation.addAttribute("table:use-banding-rows-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstColumnStyles)) { tableTagInformation.addAttribute("table:use-first-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstRowStyles)) { tableTagInformation.addAttribute("table:use-first-row-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastColumnStyles)) { tableTagInformation.addAttribute("table:use-last-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastRowStyles)) { tableTagInformation.addAttribute("table:use-last-row-styles", "true"); } int firstColumn = 0; int lastColumn = table->columns() -1; int firstRow = 0; int lastRow = table->rows() -1; if (to != -1 && from >= table->firstPosition() && to <= table->lastPosition()) { firstColumn = table->cellAt(from).column(); firstRow = table->cellAt(from).row(); lastColumn = table->cellAt(to).column(); lastRow = table->cellAt(to).row(); if (firstColumn == lastColumn && firstRow == lastRow && from >= table->firstPosition()) { // we only selected something inside a single cell so don't save a table writeBlocks(table->document(), from, to, listStyles, table); return; } } openTagRegion(KoTextWriter::Private::Table, tableTagInformation); for (int c = firstColumn ; c <= lastColumn; c++) { KoTableColumnStyle columnStyle = tcarManager.columnStyle(c); int repetition = 0; for (; repetition <= (lastColumn - c) ; repetition++) { if (columnStyle != tcarManager.columnStyle(c + repetition + 1)) break; } TagInformation tableColumnInformation; tableColumnInformation.setTagName("table:table-column"); QString columnStyleName = saveTableColumnStyle(columnStyle, c, tableStyleName); tableColumnInformation.addAttribute("table:style-name", columnStyleName); if (repetition > 0) tableColumnInformation.addAttribute("table:number-columns-repeated", repetition + 1); openTagRegion(KoTextWriter::Private::TableColumn, tableColumnInformation); closeTagRegion(); c += repetition; } if (numberHeadingRows) writer->startElement("table:table-header-rows"); // TODO make work for copying part of table that has header rows - copy header rows additionally or not ? for (int r = firstRow; r <= lastRow; r++) { TagInformation tableRowInformation; tableRowInformation.setTagName("table:table-row"); KoTableRowStyle rowStyle = tcarManager.rowStyle(r); if (!rowStyle.isEmpty()) { QString rowStyleName = saveTableRowStyle(rowStyle, r, tableStyleName); tableRowInformation.addAttribute("table:style-name", rowStyleName); } openTagRegion(KoTextWriter::Private::TableRow, tableRowInformation); for (int c = firstColumn; c <= lastColumn; c++) { QTextTableCell cell = table->cellAt(r, c); TagInformation tableCellInformation; if ((cell.row() == r) && (cell.column() == c)) { tableCellInformation.setTagName("table:table-cell"); if (cell.rowSpan() > 1) tableCellInformation.addAttribute("table:number-rows-spanned", cell.rowSpan()); if (cell.columnSpan() > 1) tableCellInformation.addAttribute("table:number-columns-spanned", cell.columnSpan()); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } // Save the Rdf for the table cell QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); QVariant v = cellFormat.property(KoTableCellStyle::InlineRdf); if (KoTextInlineRdf* inlineRdf = v.value()) { inlineRdf->saveOdf(context, writer); } QString cellStyleName = saveTableCellStyle(cellFormat, c, tableStyleName); tableCellInformation.addAttribute("table:style-name", cellStyleName); openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); writeBlocks(table->document(), cell.firstPosition(), cell.lastPosition(), listStyles, table); } else { tableCellInformation.setTagName("table:covered-table-cell"); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); } closeTagRegion(); } closeTagRegion(); if (r + 1 == numberHeadingRows) { writer->endElement(); // table:table-header-rows writer->startElement("table:table-rows"); } } if (numberHeadingRows) writer->endElement(); // table:table-rows closeTagRegion(); } void KoTextWriter::Private::saveTableOfContents(QTextDocument *document, QHash &listStyles, QTextBlock toc) { Q_UNUSED(document); writer->startElement("text:table-of-content"); KoTableOfContentsGeneratorInfo *info = toc.blockFormat().property(KoParagraphStyle::TableOfContentsData).value(); QTextDocument *tocDocument = toc.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = tocDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writer->addAttribute("text:name", QString("%1_Head").arg(info->m_name)); writeBlocks(tocDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(tocDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:table-of-content } void KoTextWriter::Private::saveBibliography(QTextDocument *document, QHash &listStyles, QTextBlock bib) { Q_UNUSED(document); writer->startElement("text:bibliography"); KoBibliographyInfo *info = bib.blockFormat().property(KoParagraphStyle::BibliographyData).value(); QTextDocument *bibDocument = bib.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = bibDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writeBlocks(bibDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(bibDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:bibliography } QTextBlock& KoTextWriter::Private::saveList(QTextBlock &block, QHash &listStyles, int level, QTextTable *currentTable) { QTextList *textList, *topLevelTextList; topLevelTextList = textList = block.textList(); int headingLevel = 0, numberedParagraphLevel = 0; QTextBlockFormat blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); KoTextDocument textDocument(block.document()); KoList *list = textDocument.list(block); int topListLevel = KoList::level(block); bool listStarted = false; if (!headingLevel && !numberedParagraphLevel) { listStarted = true; TagInformation listTagInformation; listTagInformation.setTagName("text:list"); listTagInformation.addAttribute("text:style-name", listStyles[textList]); if (list && listXmlIds.contains(list->listContinuedFrom())) { listTagInformation.addAttribute("text:continue-list", listXmlIds.value(list->listContinuedFrom())); } QString listXmlId = QString("list-%1").arg(createXmlId()); listTagInformation.addAttribute("xml:id", listXmlId); if (! listXmlIds.contains(list)) { listXmlIds.insert(list, listXmlId); } openTagRegion(KoTextWriter::Private::List, listTagInformation); } if (!headingLevel) { do { if (numberedParagraphLevel) { TagInformation paraTagInformation; paraTagInformation.setTagName("text:numbered-paragraph"); paraTagInformation.addAttribute("text:level", numberedParagraphLevel); paraTagInformation.addAttribute("text:style-name", listStyles.value(textList)); QString listId = numberedParagraphListIds.value(list, QString("list-%1").arg(createXmlId())); numberedParagraphListIds.insert(list, listId); paraTagInformation.addAttribute("text:list-id", listId); openTagRegion(KoTextWriter::Private::NumberedParagraph, paraTagInformation); writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); closeTagRegion(); } else { const bool listHeader = blockFormat.boolProperty(KoParagraphStyle::IsListHeader)|| blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem); TagInformation listItemTagInformation; listItemTagInformation.setTagName(listHeader ? "text:list-header" : "text:list-item"); if (block.blockFormat().hasProperty(KoParagraphStyle::ListStartValue)) { int startValue = block.blockFormat().intProperty(KoParagraphStyle::ListStartValue); listItemTagInformation.addAttribute("text:start-value", startValue); } if (textList == topLevelTextList) { openTagRegion(KoTextWriter::Private::ListItem, listItemTagInformation); } else { // This is a sub-list. So check for a list-change openTagRegion(KoTextWriter::Private::List, listItemTagInformation); } if (KoListStyle::isNumberingStyle(textList->format().style())) { KoTextBlockData blockData(block); writer->startElement("text:number", false); writer->addTextSpan(blockData.counterText()); writer->endElement(); } if (topListLevel == level && textList == topLevelTextList) { writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); // we are generating a text:list-item. Look forward and generate unnumbered list items. while (true) { QTextBlock nextBlock = block.next(); if (!nextBlock.textList() || !nextBlock.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) break; block = nextBlock; saveParagraph(block, block.position(), block.position() + block.length() - 1); } } else { //This is a sub-list while (KoList::level(block) >= (level + 1) && !(headingLevel || numberedParagraphLevel)) { block = saveList(block, listStyles, level + 1, currentTable); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); } //saveList will return a block one-past the last block of the list. //Since we are doing a block.next() below, we need to go one back. block = block.previous(); } closeTagRegion(); } block = block.next(); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); textList = block.textList(); } while ((textDocument.list(block) == list) && (KoList::level(block) >= topListLevel)); } if (listStarted) { closeTagRegion(); } return block; } void KoTextWriter::Private::addNameSpaceDefinitions(QString &generatedXmlString) { //Generate the name-space definitions so that it can be parsed. Like what is office:text, office:delta etc QString nameSpaceDefinitions; QTextStream nameSpacesStream(&nameSpaceDefinitions); nameSpacesStream.setCodec("UTF-8"); nameSpacesStream << ""; generatedXmlString.prepend(nameSpaceDefinitions); generatedXmlString.append(""); } -void KoTextWriter::Private::writeAttributes(QTextStream &outputXmlStream, KoXmlElement &element) +void KoTextWriter::Private::writeAttributes(QTextStream &, KoXmlElement &) { - QList > attributes = element.attributeFullNames(); - foreach (const Attribute &attributeNamePair, attributes) { - if (attributeNamePair.first == KoXmlNS::text) { - outputXmlStream << " text:" << attributeNamePair.second << "="; - outputXmlStream << "\"" << element.attributeNS(KoXmlNS::text, attributeNamePair.second) << "\""; - } else { - //To Be Added when needed - } - } } void KoTextWriter::Private::writeNode(QTextStream &outputXmlStream, KoXmlNode &node, bool writeOnlyChildren) { if (node.isText()) { outputXmlStream << node.toText().data(); } else if (node.isElement()) { KoXmlElement element = node.toElement(); - if ((element.localName() == "removed-content") && !element.childNodesCount()) { + if ((element.localName() == "removed-content") && !KoXml::childNodesCount(element)) { return; } if (!writeOnlyChildren) { outputXmlStream << "<" << element.prefix() << ":" << element.localName(); writeAttributes(outputXmlStream,element); outputXmlStream << ">"; } for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { writeNode(outputXmlStream, node); } if (!writeOnlyChildren) { outputXmlStream << ""; } } } QString KoTextWriter::Private::createXmlId() { QString uuid = QUuid::createUuid().toString(); uuid.remove('{'); uuid.remove('}'); return uuid; } diff --git a/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp b/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp index 0005a3f5e9..b9e3ba7d02 100644 --- a/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp @@ -1,1057 +1,1051 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Pierre Ducroquet * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTableCellStyle.h" #include "KoTableCellStyle_p.h" #include #include #include #include #include "KoParagraphStyle.h" #include #include #include #include #include #include #include #include #include "TextDebug.h" #include KoTableCellStyle::RotationAlignment rotationAlignmentFromString(const QString& align) { if (align == "bottom") return KoTableCellStyle::RAlignBottom; if (align == "center") return KoTableCellStyle::RAlignCenter; if (align == "top") return KoTableCellStyle::RAlignTop; - + return KoTableCellStyle::RAlignNone; } QString rotationAlignmentToString(KoTableCellStyle::RotationAlignment align) { if (align == KoTableCellStyle::RAlignBottom) return "bottom"; if (align == KoTableCellStyle::RAlignTop) return "top"; if (align == KoTableCellStyle::RAlignCenter) return "center"; return "none"; } KoTableCellStylePrivate::KoTableCellStylePrivate() : paragraphStyle(0) , parentStyle(0) , next(0) { } KoTableCellStylePrivate::~KoTableCellStylePrivate() { } void KoTableCellStylePrivate::setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } KoTableCellStyle::KoTableCellStyle(QObject *parent) : QObject(parent) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); d->paragraphStyle = new KoParagraphStyle(this); } KoTableCellStyle::KoTableCellStyle(const QTextTableCellFormat &format, QObject *parent) : QObject(parent) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); d->stylesPrivate = format.properties(); d->paragraphStyle = new KoParagraphStyle(this); } KoTableCellStyle::KoTableCellStyle(const KoTableCellStyle &other) :QObject(other.parent()) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); copyProperties(&other); d->paragraphStyle = other.paragraphStyle()->clone(this); } KoTableCellStyle& KoTableCellStyle::operator=(const KoTableCellStyle &other) { Q_D(KoTableCellStyle); if (this == &other) { return *this; } copyProperties(&other); d->paragraphStyle = other.paragraphStyle()->clone(this); return *this; } KoTableCellStyle::~KoTableCellStyle() { delete d_ptr; } KoTableCellStyle *KoTableCellStyle::fromTableCell(const QTextTableCell &tableCell, QObject *parent) { QTextTableCellFormat tableCellFormat = tableCell.format().toTableCellFormat(); return new KoTableCellStyle(tableCellFormat, parent); } QTextCharFormat KoTableCellStyle::cleanCharFormat(const QTextCharFormat &charFormat) { if (charFormat.isTableCellFormat()) { QTextTableCellFormat format; const QMap props = charFormat.properties(); QMap::const_iterator it = props.begin(); while (it != props.end()) { // lets save all Qt's table cell properties if (it.key()>=QTextFormat::TableCellRowSpan && it.key()=StyleId && it.key()parentStyle = parent; } void KoTableCellStyle::setLeftPadding(qreal padding) { setProperty(QTextFormat::TableCellLeftPadding, padding); } void KoTableCellStyle::setTopPadding(qreal padding) { setProperty(QTextFormat::TableCellTopPadding, padding); } void KoTableCellStyle::setRightPadding(qreal padding) { setProperty(QTextFormat::TableCellRightPadding, padding); } void KoTableCellStyle::setBottomPadding(qreal padding) { setProperty(QTextFormat::TableCellBottomPadding, padding); } qreal KoTableCellStyle::leftPadding() const { return propertyDouble(QTextFormat::TableCellLeftPadding); } qreal KoTableCellStyle::rightPadding() const { return propertyDouble(QTextFormat::TableCellRightPadding); } qreal KoTableCellStyle::topPadding() const { return propertyDouble(QTextFormat::TableCellTopPadding); } qreal KoTableCellStyle::bottomPadding() const { return propertyDouble(QTextFormat::TableCellBottomPadding); } void KoTableCellStyle::setPadding(qreal padding) { setBottomPadding(padding); setTopPadding(padding); setRightPadding(padding); setLeftPadding(padding); } KoParagraphStyle *KoTableCellStyle::paragraphStyle() const { Q_D(const KoTableCellStyle); return d->paragraphStyle; } bool KoTableCellStyle::shrinkToFit() const { return propertyBoolean(ShrinkToFit); } void KoTableCellStyle::setShrinkToFit(bool state) { setProperty(ShrinkToFit, state); } void KoTableCellStyle::setProperty(int key, const QVariant &value) { Q_D(KoTableCellStyle); if (d->parentStyle) { QVariant var = d->parentStyle->value(key); if (!var.isNull() && var == value) { // same as parent, so its actually a reset. d->stylesPrivate.remove(key); return; } } d->stylesPrivate.add(key, value); } void KoTableCellStyle::remove(int key) { Q_D(KoTableCellStyle); d->stylesPrivate.remove(key); } QVariant KoTableCellStyle::value(int key) const { Q_D(const KoTableCellStyle); QVariant var = d->stylesPrivate.value(key); if (var.isNull() && d->parentStyle) var = d->parentStyle->value(key); return var; } bool KoTableCellStyle::hasProperty(int key) const { Q_D(const KoTableCellStyle); return d->stylesPrivate.contains(key); } qreal KoTableCellStyle::propertyDouble(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QPen KoTableCellStyle::propertyPen(int key) const { const QVariant prop = value(key); if (prop.userType() != QVariant::Pen) return QPen(Qt::NoPen); return qvariant_cast(prop); } int KoTableCellStyle::propertyInt(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool KoTableCellStyle::propertyBoolean(int key) const { QVariant variant = value(key); if (variant.isNull()) return false; return variant.toBool(); } QColor KoTableCellStyle::propertyColor(int key) const { QVariant variant = value(key); if (variant.isNull()) { return QColor(); } return qvariant_cast(variant); } void KoTableCellStyle::applyStyle(QTextTableCellFormat &format) const { Q_D(const KoTableCellStyle); if (d->parentStyle) { d->parentStyle->applyStyle(format); } QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); format.setProperty(keys[i], variant); } // Hack : build KoBorder here if (d->parentStyle && d->parentStyle->hasProperty(Borders) && this->hasProperty(Borders)) { KoBorder parentBorder = d->parentStyle->borders(); KoBorder childBorder = this->borders(); if (childBorder.hasBorder(KoBorder::LeftBorder)) parentBorder.setBorderData(KoBorder::LeftBorder, childBorder.borderData(KoBorder::LeftBorder)); if (childBorder.hasBorder(KoBorder::RightBorder)) parentBorder.setBorderData(KoBorder::RightBorder, childBorder.borderData(KoBorder::RightBorder)); if (childBorder.hasBorder(KoBorder::TopBorder)) parentBorder.setBorderData(KoBorder::TopBorder, childBorder.borderData(KoBorder::TopBorder)); if (childBorder.hasBorder(KoBorder::BottomBorder)) parentBorder.setBorderData(KoBorder::BottomBorder, childBorder.borderData(KoBorder::BottomBorder)); if (childBorder.hasBorder(KoBorder::BltrBorder)) parentBorder.setBorderData(KoBorder::BltrBorder, childBorder.borderData(KoBorder::BltrBorder)); if (childBorder.hasBorder(KoBorder::TlbrBorder)) parentBorder.setBorderData(KoBorder::TlbrBorder, childBorder.borderData(KoBorder::TlbrBorder)); format.setProperty(Borders, QVariant::fromValue(parentBorder)); } } void KoTableCellStyle::applyStyle(QTextTableCell &cell) const { Q_D(const KoTableCellStyle); QTextTableCellFormat format = cell.format().toTableCellFormat(); applyStyle(format); if (d->paragraphStyle) { d->paragraphStyle->KoCharacterStyle::applyStyle(format); } cell.setFormat(format); } void KoTableCellStyle::setBackground(const QBrush &brush) { setProperty(CellBackgroundBrush, brush); } void KoTableCellStyle::clearBackground() { Q_D(KoTableCellStyle); d->stylesPrivate.remove(CellBackgroundBrush); } QBrush KoTableCellStyle::background() const { Q_D(const KoTableCellStyle); QVariant variant = d->stylesPrivate.value(CellBackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } void KoTableCellStyle::setWrap(bool state) { setProperty(Wrap, state); } bool KoTableCellStyle::wrap() const { return propertyBoolean(Wrap); } void KoTableCellStyle::setAlignment(Qt::Alignment alignment) { setProperty(VerticalAlignment, (int) alignment); } Qt::Alignment KoTableCellStyle::alignment() const { if (propertyInt(VerticalAlignment) == 0) return Qt::AlignTop; return static_cast(propertyInt(VerticalAlignment)); } KoTableCellStyle *KoTableCellStyle::parentStyle() const { Q_D(const KoTableCellStyle); return d->parentStyle; } QString KoTableCellStyle::name() const { Q_D(const KoTableCellStyle); return d->name; } void KoTableCellStyle::setName(const QString &name) { Q_D(KoTableCellStyle); if (name == d->name) return; d->name = name; emit nameChanged(name); } int KoTableCellStyle::styleId() const { return propertyInt(StyleId); } void KoTableCellStyle::setStyleId(int id) { Q_D(KoTableCellStyle); setProperty(StyleId, id); if (d->next == 0) d->next = id; } QString KoTableCellStyle::masterPageName() const { return value(MasterPageName).toString(); } void KoTableCellStyle::setMasterPageName(const QString &name) { setProperty(MasterPageName, name); } void KoTableCellStyle::setCellProtection(KoTableCellStyle::CellProtectionFlag protection) { setProperty(CellProtection, protection); } KoTableCellStyle::CellProtectionFlag KoTableCellStyle::cellProtection() const { return (CellProtectionFlag) propertyInt(CellProtection); } void KoTableCellStyle::setTextDirection(KoText::Direction value) { setProperty(TextWritingMode, value); } KoText::Direction KoTableCellStyle::textDirection() const { return (KoText::Direction) propertyInt(TextWritingMode); } bool KoTableCellStyle::printContent() const { return (hasProperty(PrintContent) && propertyBoolean(PrintContent)); } void KoTableCellStyle::setPrintContent(bool state) { setProperty(PrintContent, state); } bool KoTableCellStyle::repeatContent() const { return (hasProperty(RepeatContent) && propertyBoolean(RepeatContent)); } void KoTableCellStyle::setRepeatContent(bool state) { setProperty(RepeatContent, state); } int KoTableCellStyle::decimalPlaces() const { return propertyInt(DecimalPlaces); } void KoTableCellStyle::setDecimalPlaces(int places) { setProperty(DecimalPlaces, places); } bool KoTableCellStyle::alignFromType() const { return (hasProperty(AlignFromType) && propertyBoolean(AlignFromType)); } void KoTableCellStyle::setAlignFromType(bool state) { setProperty(AlignFromType, state); } qreal KoTableCellStyle::rotationAngle() const { return propertyDouble(RotationAngle); } void KoTableCellStyle::setRotationAngle(qreal value) { if (value >= 0) setProperty(RotationAngle, value); } void KoTableCellStyle::setVerticalGlyphOrientation(bool state) { setProperty(VerticalGlyphOrientation, state); } bool KoTableCellStyle::verticalGlyphOrientation() const { if (hasProperty(VerticalGlyphOrientation)) return propertyBoolean(VerticalGlyphOrientation); return true; } void KoTableCellStyle::setDirection(KoTableCellStyle::CellTextDirection direction) { setProperty(Direction, direction); } KoBorder KoTableCellStyle::borders() const { if (hasProperty(Borders)) return value(Borders).value(); return KoBorder(); } void KoTableCellStyle::setBorders(const KoBorder& borders) { setProperty(Borders, QVariant::fromValue(borders)); } KoShadowStyle KoTableCellStyle::shadow() const { if (hasProperty(Shadow)) return value(Shadow).value(); return KoShadowStyle(); } void KoTableCellStyle::setShadow(const KoShadowStyle& shadow) { setProperty(Shadow, QVariant::fromValue(shadow)); } KoTableCellStyle::RotationAlignment KoTableCellStyle::rotationAlignment() const { return static_cast(propertyInt(RotationAlign)); } void KoTableCellStyle::setRotationAlignment(KoTableCellStyle::RotationAlignment align) { setProperty(RotationAlign, align); } KoTableCellStyle::CellTextDirection KoTableCellStyle::direction() const { if (hasProperty(Direction)) return (KoTableCellStyle::CellTextDirection) propertyInt(Direction); return KoTableCellStyle::Default; } void KoTableCellStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); Q_D(KoTableCellStyle); if (element->hasAttributeNS(KoXmlNS::style, "display-name")) d->name = element->attributeNS(KoXmlNS::style, "display-name", QString()); if (d->name.isEmpty()) // if no style:display-name is given us the style:name d->name = element->attributeNS(KoXmlNS::style, "name", QString()); QString masterPage = element->attributeNS(KoXmlNS::style, "master-page-name", QString()); if (! masterPage.isEmpty()) { setMasterPageName(masterPage); } paragraphStyle()->loadOdf(element, scontext, true); // load the par and char properties - - // Borders - we don't handle inheritance unfortunately - hope it's not a big problem - KoBorder borders = this->borders(); - borders.loadOdf(element->namedItemNS(KoXmlNS::style, "table-cell-properties").toElement()); - setBorders(borders); - context.styleStack().save(); QString family = element->attributeNS(KoXmlNS::style, "family", "table-cell"); context.addStyles(element, family.toLocal8Bit().constData()); // Load all parents - only because we don't support inheritance. context.styleStack().setTypeProperties("table-cell"); loadOdfProperties(scontext, context.styleStack()); context.styleStack().setTypeProperties("graphic"); loadOdfProperties(scontext, context.styleStack()); context.styleStack().restore(); } void KoTableCellStyle::loadOdfProperties(KoShapeLoadingContext &context, KoStyleStack &styleStack) { // Padding if (styleStack.hasProperty(KoXmlNS::fo, "padding-left")) setLeftPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-left"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-right")) setRightPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-right"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-top")) setTopPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-top"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-bottom")) setBottomPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-bottom"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding")) setPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding"))); if (styleStack.hasProperty(KoXmlNS::style, "shadow")) { KoShadowStyle shadow; if (shadow.loadOdf(styleStack.property(KoXmlNS::style, "shadow"))) { setShadow(shadow); } } // The fo:background-color attribute specifies the background color of a cell. if (styleStack.hasProperty(KoXmlNS::fo, "background-color")) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") setBackground(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format setBackground(brush); } } QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "solid" || fillStyle == "hatch") { styleStack.save(); QBrush brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fillStyle, context.odfLoadingContext().stylesReader()); setBackground(brush); styleStack.restore(); } if (styleStack.hasProperty(KoXmlNS::style, "shrink-to-fit")) { setShrinkToFit(styleStack.property(KoXmlNS::style, "shrink-to-fit") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "print-content")) { setPrintContent(styleStack.property(KoXmlNS::style, "print-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "repeat-content")) { setRepeatContent(styleStack.property(KoXmlNS::style, "repeat-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "repeat-content")) { setRepeatContent(styleStack.property(KoXmlNS::style, "repeat-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "decimal-places")) { bool ok; int value = styleStack.property(KoXmlNS::style, "decimal-places").toInt(&ok); if (ok) setDecimalPlaces(value); } - + if (styleStack.hasProperty(KoXmlNS::style, "rotation-angle")) { setRotationAngle(KoUnit::parseAngle(styleStack.property(KoXmlNS::style, "rotation-angle"))); } - + if (styleStack.hasProperty(KoXmlNS::style, "glyph-orientation-vertical")) { setVerticalGlyphOrientation(styleStack.property(KoXmlNS::style, "glyph-orientation-vertical") == "auto"); } - + if (styleStack.hasProperty(KoXmlNS::style, "direction")) { if (styleStack.property(KoXmlNS::style, "direction") == "ltr") setDirection(KoTableCellStyle::LeftToRight); else setDirection(KoTableCellStyle::TopToBottom); } - + if (styleStack.hasProperty(KoXmlNS::style, "rotation-align")) { setRotationAlignment(rotationAlignmentFromString(styleStack.property(KoXmlNS::style, "rotation-align"))); } - + if (styleStack.hasProperty(KoXmlNS::style, "text-align-source")) { setAlignFromType(styleStack.property(KoXmlNS::style, "text-align-source") == "value-type"); } - + if (styleStack.hasProperty(KoXmlNS::fo, "wrap-option")) { setWrap(styleStack.property(KoXmlNS::fo, "wrap-option") == "wrap"); } - + if (styleStack.hasProperty(KoXmlNS::style, "cell-protect")) { QString protection = styleStack.property(KoXmlNS::style, "cell-protect"); if (protection == "none") setCellProtection(NoProtection); else if (protection == "hidden-and-protected") setCellProtection(HiddenAndProtected); else if (protection == "protected") setCellProtection(Protected); else if (protection == "formula-hidden") setCellProtection(FormulaHidden); else if ((protection == "protected formula-hidden") || (protection == "formula-hidden protected")) setCellProtection(ProtectedAndFormulaHidden); } // Alignment const QString verticalAlign(styleStack.property(KoXmlNS::style, "vertical-align")); if (!verticalAlign.isEmpty()) { if (verticalAlign == "automatic") setAlignment((Qt::AlignmentFlag) 0); else setAlignment(KoText::valignmentFromString(verticalAlign)); } - + if (styleStack.hasProperty(KoXmlNS::style, "writing-mode")) setTextDirection(KoText::directionFromString(styleStack.property(KoXmlNS::style, "writing-mode"))); } void KoTableCellStyle::copyProperties(const KoTableCellStyle *style) { Q_D(KoTableCellStyle); const KoTableCellStylePrivate *styleD = static_cast(style->d_func()); d->stylesPrivate = styleD->stylesPrivate; setName(style->name()); // make sure we emit property change d->next = styleD->next; d->parentStyle = styleD->parentStyle; } KoTableCellStyle *KoTableCellStyle::clone(QObject *parent) { KoTableCellStyle *newStyle = new KoTableCellStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoTableCellStyle::operator==(const KoTableCellStyle &other) const { Q_D(const KoTableCellStyle); const KoTableCellStylePrivate *otherD = static_cast(other.d_func()); return otherD->stylesPrivate == d->stylesPrivate; } void KoTableCellStyle::removeDuplicates(const KoTableCellStyle &other) { Q_D(KoTableCellStyle); const KoTableCellStylePrivate *otherD = static_cast(other.d_func()); d->stylesPrivate.removeDuplicates(otherD->stylesPrivate); } void KoTableCellStyle::saveOdf(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoTableCellStyle); QList keys = d->stylesPrivate.keys(); bool donePadding = false; - if (hasProperty(QTextFormat::TableCellLeftPadding) && - hasProperty(QTextFormat::TableCellRightPadding) && - hasProperty(QTextFormat::TableCellTopPadding) && - hasProperty(QTextFormat::TableCellBottomPadding) && + if (hasProperty(QTextFormat::TableCellLeftPadding) && + hasProperty(QTextFormat::TableCellRightPadding) && + hasProperty(QTextFormat::TableCellTopPadding) && + hasProperty(QTextFormat::TableCellBottomPadding) && leftPadding() == rightPadding() && topPadding() == bottomPadding() && topPadding() == leftPadding()) { donePadding = true; style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::TableCellType); } Q_FOREACH (int key, keys) { if (key == CellBackgroundBrush) { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::TableCellType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::TableCellType); } else if (key == VerticalAlignment) { if (propertyInt(VerticalAlignment) == 0) style.addProperty("style:vertical-align", "automatic", KoGenStyle::TableCellType); else style.addProperty("style:vertical-align", KoText::valignmentToString(alignment()), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellLeftPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellRightPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellTopPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellBottomPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::TableCellType); } else if (key == ShrinkToFit) { style.addProperty("style:shrink-to-fit", shrinkToFit(), KoGenStyle::TableCellType); } else if (key == PrintContent) { style.addProperty("style:print-content", printContent(), KoGenStyle::TableCellType); } else if (key == RepeatContent) { style.addProperty("style:repeat-content", repeatContent(), KoGenStyle::TableCellType); } else if (key == DecimalPlaces) { style.addProperty("style:decimal-places", decimalPlaces(), KoGenStyle::TableCellType); } else if (key == RotationAngle) { style.addProperty("style:rotation-angle", QString::number(rotationAngle()), KoGenStyle::TableCellType); } else if (key == Wrap) { if (wrap()) style.addProperty("fo:wrap-option", "wrap", KoGenStyle::TableCellType); else style.addProperty("fo:wrap-option", "no-wrap", KoGenStyle::TableCellType); } else if (key == Direction) { if (direction() == LeftToRight) style.addProperty("style:direction", "ltr", KoGenStyle::TableCellType); else if (direction() == TopToBottom) style.addProperty("style:direction", "ttb", KoGenStyle::TableCellType); } else if (key == CellProtection) { if (cellProtection() == NoProtection) style.addProperty("style:cell-protect", "none", KoGenStyle::TableCellType); else if (cellProtection() == HiddenAndProtected) style.addProperty("style:cell-protect", "hidden-and-protected", KoGenStyle::TableCellType); else if (cellProtection() == Protected) style.addProperty("style:cell-protect", "protected", KoGenStyle::TableCellType); else if (cellProtection() == FormulaHidden) style.addProperty("style:cell-protect", "formula-hidden", KoGenStyle::TableCellType); else if (cellProtection() == ProtectedAndFormulaHidden) style.addProperty("style:cell-protect", "protected formula-hidden", KoGenStyle::TableCellType); } else if (key == AlignFromType) { if (alignFromType()) style.addProperty("style:text-align-source", "value-type", KoGenStyle::TableCellType); else style.addProperty("style:text-align-source", "fix", KoGenStyle::TableCellType); } else if (key == RotationAlign) { style.addProperty("style:rotation-align", rotationAlignmentToString(rotationAlignment()), KoGenStyle::TableCellType); } else if (key == TextWritingMode) { style.addProperty("style:writing-mode", KoText::directionToString(textDirection()), KoGenStyle::TableCellType); } else if (key == VerticalGlyphOrientation) { if (verticalGlyphOrientation()) style.addProperty("style:glyph-orientation-vertical", "auto", KoGenStyle::TableCellType); else style.addProperty("style:glyph-orientation-vertical", "0", KoGenStyle::TableCellType); } else if (key == Borders) { borders().saveOdf(style, KoGenStyle::TableCellType); } else if (key == Shadow) { style.addProperty("style:shadow", shadow().saveOdf()); } } if (d->paragraphStyle) { d->paragraphStyle->saveOdf(style, context); } } void KoTableCellStyle::setEdge(KoBorder::BorderSide side, KoBorder::BorderStyle style, qreal width, const QColor &color) { KoBorder::BorderData edge; qreal innerWidth = 0; qreal middleWidth = 0; qreal space = 0; QVector dashes; switch (style) { case KoBorder::BorderNone: width = 0.0; break; case KoBorder::BorderDouble: innerWidth = space = width/3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDotted: dashes << 1 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashed: dashes << 4 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashedLong: { dashes << 4 << 4; edge.outerPen.setDashPattern(dashes); break; } case KoBorder::BorderTriple: innerWidth = middleWidth = space = width/6; width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDashDot: dashes << 3 << 3<< 7 << 3; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashDotDot: dashes << 2 << 2<< 6 << 2 << 2 << 2; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderWave: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderSlash: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDoubleWave: innerWidth = space = width/3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; default: edge.outerPen.setStyle(Qt::SolidLine); break; } edge.outerPen.setColor(color); edge.outerPen.setJoinStyle(Qt::MiterJoin); edge.outerPen.setCapStyle(Qt::FlatCap); edge.outerPen.setWidthF(width); edge.spacing = space; edge.innerPen = edge.outerPen; edge.innerPen.setWidthF(innerWidth); QPen middlePen; middlePen = edge.outerPen; middlePen.setWidthF(middleWidth); setEdge(side, edge, style); } void KoTableCellStyle::setEdge(KoBorder::BorderSide side, const KoBorder::BorderData &edge, KoBorder::BorderStyle style) { KoBorder borders = this->borders(); KoBorder::BorderData edgeCopy(edge); edgeCopy.style = style; // Just for safety. borders.setBorderData(side, edgeCopy); setBorders(borders); } void KoTableCellStyle::setEdgeDoubleBorderValues(KoBorder::BorderSide side, qreal innerWidth, qreal space) { KoBorder::BorderData edge = getEdge(side); qreal totalWidth = edge.outerPen.widthF() + edge.spacing + edge.innerPen.widthF(); if (edge.innerPen.widthF() > 0.0) { edge.outerPen.setWidthF(totalWidth - innerWidth - space); edge.spacing = space; edge.innerPen.setWidthF(innerWidth); setEdge(side, edge, getBorderStyle(side)); } } bool KoTableCellStyle::hasBorders() const { return borders().hasBorder(); } qreal KoTableCellStyle::leftBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::rightBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::topBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::bottomBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::leftInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::rightInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::topInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::bottomInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::leftOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::rightOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::topOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::bottomOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.outerPen.widthF(); } KoBorder::BorderData KoTableCellStyle::getEdge(KoBorder::BorderSide side) const { KoBorder border = this->borders(); return border.borderData(side); } KoBorder::BorderStyle KoTableCellStyle::getBorderStyle(KoBorder::BorderSide side) const { KoBorder::BorderData edge = getEdge(side); return edge.style; } diff --git a/plugins/impex/CMakeLists.txt b/plugins/impex/CMakeLists.txt index f91943f646..eb2a2e8f8b 100644 --- a/plugins/impex/CMakeLists.txt +++ b/plugins/impex/CMakeLists.txt @@ -1,48 +1,48 @@ project(kritafilters) add_subdirectory(libkra) if(CMAKE_SIZEOF_VOID_P EQUAL 4) add_definitions( -DCPU_32_BITS ) endif() if(JPEG_FOUND AND HAVE_LCMS2) add_subdirectory(jpeg) endif() if(TIFF_FOUND) add_subdirectory(tiff) endif() if(PNG_FOUND) add_subdirectory(png) add_subdirectory(csv) endif() if(OPENEXR_FOUND) add_subdirectory(exr) endif() -if(POPPLER_FOUND) +if(Poppler_Qt5_FOUND) add_subdirectory(pdf) endif() if(LIBRAW_FOUND) add_subdirectory(raw) endif() add_subdirectory(svg) add_subdirectory(bmp) add_subdirectory(ora) add_subdirectory(ppm) add_subdirectory(xcf) add_subdirectory(psd) add_subdirectory(odg) add_subdirectory(qml) add_subdirectory(tga) add_subdirectory(heightmap) add_subdirectory(brush) add_subdirectory(spriter) add_subdirectory(video) add_subdirectory(kra) diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc b/plugins/impex/jpeg/kis_jpeg_converter.cc index f6ddbb7c46..6435bf0d4f 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.cc +++ b/plugins/impex/jpeg/kis_jpeg_converter.cc @@ -1,731 +1,732 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_jpeg_converter.h" #include #include #include #ifdef HAVE_LCMS2 # include #else # include #endif extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorModelStandardIds.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ #define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) const char photoshopMarker[] = "Photoshop 3.0\0"; //const char photoshopBimId_[] = "8BIM"; const uint16_t photoshopIptc = 0x0404; const char xmpMarker[] = "http://ns.adobe.com/xap/1.0/\0"; const QByteArray photoshopIptc_((char*)&photoshopIptc, 2); namespace { void jpegErrorExit ( j_common_ptr cinfo ) { char jpegLastErrorMsg[JMSG_LENGTH_MAX]; /* Create the message */ ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg ); /* Jump to the setjmp point */ throw std::runtime_error( jpegLastErrorMsg ); // or your preffered exception ... } J_COLOR_SPACE getColorTypeforColorSpace(const KoColorSpace * cs) { if (KoID(cs->id()) == KoID("GRAYA") || cs->id() == "GRAYAU16" || cs->id() == "GRAYA16") { return JCS_GRAYSCALE; } if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { return JCS_RGB; } if (KoID(cs->id()) == KoID("CMYK") || KoID(cs->id()) == KoID("CMYKAU16")) { return JCS_CMYK; } return JCS_UNKNOWN; } QString getColorSpaceModelForColorType(J_COLOR_SPACE color_type) { dbgFile << "color_type =" << color_type; if (color_type == JCS_GRAYSCALE) { return GrayAColorModelID.id(); } else if (color_type == JCS_RGB) { return RGBAColorModelID.id(); } else if (color_type == JCS_CMYK) { return CMYKAColorModelID.id(); } return ""; } } struct KisJPEGConverter::Private { Private(KisDocument *doc, bool batchMode) : doc(doc), stop(false), batchMode(batchMode) {} KisImageSP image; KisDocument *doc; bool stop; bool batchMode; }; KisJPEGConverter::KisJPEGConverter(KisDocument *doc, bool batchMode) : m_d(new Private(doc, batchMode)) { } KisJPEGConverter::~KisJPEGConverter() { } KisImageBuilder_Result KisJPEGConverter::decode(QIODevice *io) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpegErrorExit; try { jpeg_create_decompress(&cinfo); KisJPEGSource::setSource(&cinfo, io); jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); /* Save APP0..APP15 markers */ for (int m = 0; m < 16; m++) jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); // setup_read_icc_profile(&cinfo); // read header jpeg_read_header(&cinfo, (boolean)true); // start reading jpeg_start_decompress(&cinfo); // Get the colorspace QString modelId = getColorSpaceModelForColorType(cinfo.out_color_space); if (modelId.isEmpty()) { dbgFile << "unsupported colorspace :" << cinfo.out_color_space; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } uchar* profile_data; uint profile_len; const KoColorProfile* profile = 0; QByteArray profile_rawdata; if (read_icc_profile(&cinfo, &profile_data, &profile_len)) { profile_rawdata.resize(profile_len); memcpy(profile_rawdata.data(), profile_data, profile_len); cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, profile_len); if (hProfile != (cmsHPROFILE) 0) { profile = KoColorSpaceRegistry::instance()->createColorProfile(modelId, Integer8BitsColorDepthID.id(), profile_rawdata); Q_CHECK_PTR(profile); dbgFile <<"profile name:" << profile->name() <<" product information:" << profile->info(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } } + const QString colorSpaceId = + KoColorSpaceRegistry::instance()->colorSpaceId(modelId, Integer8BitsColorDepthID.id()); + // Check that the profile is used by the color space - if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory( - KoColorSpaceRegistry::instance()->colorSpaceId( - modelId, Integer8BitsColorDepthID.id()))->profileIsCompatible(profile)) { + if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << modelId; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile); } else cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), ""); if (cs == 0) { dbgFile << "unknown colorspace"; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // TODO fixit // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Apparently an invalid transform was created from the profile. See bug https://bugs.kde.org/show_bug.cgi?id=255451. // After 2.3: warn the user! if (transform && !transform->isValid()) { delete transform; transform = 0; } // Creating the KisImageSP if (!m_d->image) { m_d->image = new KisImage(m_d->doc->createUndoStore(), cinfo.image_width, cinfo.image_height, cs, "built image"); Q_CHECK_PTR(m_d->image); } // Set resolution double xres = 72, yres = 72; if (cinfo.density_unit == 1) { xres = cinfo.X_density; yres = cinfo.Y_density; } else if (cinfo.density_unit == 2) { xres = cinfo.X_density * 2.54; yres = cinfo.Y_density * 2.54; } if (xres < 72) { xres = 72; } if (yres < 72) { yres = 72; } m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points // Create layer KisPaintLayerSP layer = KisPaintLayerSP(new KisPaintLayer(m_d->image.data(), m_d->image -> nextLayerName(), quint8_MAX)); // Read data JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width*cinfo.num_components]; for (; cinfo.output_scanline < cinfo.image_height;) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, cinfo.output_scanline, cinfo.image_width); jpeg_read_scanlines(&cinfo, &row_pointer, 1); quint8 *src = row_pointer; switch (cinfo.out_color_space) { case JCS_GRAYSCALE: do { quint8 *d = it->rawData(); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[1] = quint8_MAX; } while (it->nextPixel()); break; case JCS_RGB: do { quint8 *d = it->rawData(); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[3] = quint8_MAX; } while (it->nextPixel()); break; case JCS_CMYK: do { quint8 *d = it->rawData(); d[0] = quint8_MAX - *(src++); d[1] = quint8_MAX - *(src++); d[2] = quint8_MAX - *(src++); d[3] = quint8_MAX - *(src++); if (transform) transform->transform(d, d, 1); d[4] = quint8_MAX; } while (it->nextPixel()); break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_d->image->addNode(KisNodeSP(layer.data()), m_d->image->rootLayer().data()); // Read exif information dbgFile << "Looking for exif information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 14) { continue; /* Exif data is in an APP1 marker of at least 14 octets */ } if (GETJOCTET(marker->data[0]) != (JOCTET) 0x45 || GETJOCTET(marker->data[1]) != (JOCTET) 0x78 || GETJOCTET(marker->data[2]) != (JOCTET) 0x69 || GETJOCTET(marker->data[3]) != (JOCTET) 0x66 || GETJOCTET(marker->data[4]) != (JOCTET) 0x00 || GETJOCTET(marker->data[5]) != (JOCTET) 0x00) continue; /* no Exif header */ dbgFile << "Found exif information of length :" << marker->data_length; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QByteArray byteArray((const char*)marker->data + 6, marker->data_length - 6); QBuffer buf(&byteArray); exifIO->loadFrom(layer->metaData(), &buf); // Interpret orientation tag if (layer->metaData()->containsEntry("http://ns.adobe.com/tiff/1.0/", "Orientation")) { KisMetaData::Entry& entry = layer->metaData()->getEntry("http://ns.adobe.com/tiff/1.0/", "Orientation"); if (entry.value().type() == KisMetaData::Value::Variant) { switch (entry.value().asVariant().toInt()) { case 2: KisTransformWorker::mirrorY(layer->paintDevice()); break; case 3: image()->rotateImage(M_PI); break; case 4: KisTransformWorker::mirrorX(layer->paintDevice()); break; case 5: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorY(layer->paintDevice()); break; case 6: image()->rotateImage(M_PI / 2); break; case 7: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorX(layer->paintDevice()); break; case 8: image()->rotateImage(-M_PI / 2 + M_PI*2); break; default: break; } } entry.value().setVariant(1); } break; } dbgFile << "Looking for IPTC information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 13) || marker->data_length < 14) { continue; /* IPTC data is in an APP13 marker of at least 16 octets */ } if (memcmp(marker->data, photoshopMarker, 14) != 0) { for (int i = 0; i < 14; i++) { dbgFile << (int)(*(marker->data + i)) << "" << (int)(photoshopMarker[i]); } dbgFile << "No photoshop marker"; continue; /* No IPTC Header */ } dbgFile << "Found Photoshop information of length :" << marker->data_length; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); const Exiv2::byte *record = 0; uint32_t sizeIptc = 0; uint32_t sizeHdr = 0; // Find actual Iptc data within the APP13 segment if (!Exiv2::Photoshop::locateIptcIrb((Exiv2::byte*)(marker->data + 14), marker->data_length - 14, &record, &sizeHdr, &sizeIptc)) { if (sizeIptc) { // Decode the IPTC data QByteArray byteArray((const char*)(record + sizeHdr), sizeIptc); iptcIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); } else { dbgFile << "IPTC Not found in Photoshop marker"; } } break; } dbgFile << "Looking for XMP information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 31) { continue; /* XMP data is in an APP1 marker of at least 31 octets */ } if (memcmp(marker->data, xmpMarker, 29) != 0) { dbgFile << "Not XMP marker"; continue; /* No xmp Header */ } dbgFile << "Found XMP Marker of length " << marker->data_length; QByteArray byteArray((const char*)marker->data + 29, marker->data_length - 29); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); break; } // Dump loaded metadata layer->metaData()->debugDump(); // Check whether the metadata has resolution info, too... if (cinfo.density_unit == 0 && layer->metaData()->containsEntry("tiff:XResolution") && layer->metaData()->containsEntry("tiff:YResolution")) { double xres = layer->metaData()->getEntry("tiff:XResolution").value().asDouble(); double yres = layer->metaData()->getEntry("tiff:YResolution").value().asDouble(); if (xres != 0 && yres != 0) { m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points } } // Finish decompression jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row_pointer; return KisImageBuilder_RESULT_OK; } catch( std::runtime_error &e) { jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_FAILURE; } } KisImageBuilder_Result KisJPEGConverter::buildImage(QIODevice *io) { return decode(io); } KisImageSP KisJPEGConverter::image() { return m_d->image; } KisImageBuilder_Result KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) { if (!layer) return KisImageBuilder_RESULT_INVALID_ARG; KisImageSP image = KisImageSP(layer->image()); if (!image) return KisImageBuilder_RESULT_EMPTY; const KoColorSpace * cs = layer->colorSpace(); J_COLOR_SPACE color_type = getColorTypeforColorSpace(cs); if (color_type == JCS_UNKNOWN) { KUndo2Command *tmp = layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); delete tmp; cs = KoColorSpaceRegistry::instance()->rgb8(); color_type = JCS_RGB; } if (options.forceSRGB) { const KoColorSpace* dst = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), layer->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); KUndo2Command *tmp = layer->paintDevice()->convertTo(dst); delete tmp; cs = dst; color_type = JCS_RGB; } uint height = image->height(); uint width = image->width(); // Initialize structure struct jpeg_compress_struct cinfo; // Initialize error output struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); // Initialize output stream KisJPEGDestination::setDestination(&cinfo, io); cinfo.image_width = width; // image width and height, in pixels cinfo.image_height = height; cinfo.input_components = cs->colorChannelCount(); // number of color channels per pixel */ cinfo.in_color_space = color_type; // colorspace of input image // Set default compression parameters jpeg_set_defaults(&cinfo); // Customize them jpeg_set_quality(&cinfo, options.quality, (boolean)options.baseLineJPEG); if (options.progressive) { jpeg_simple_progression(&cinfo); } // Optimize ? cinfo.optimize_coding = (boolean)options.optimize; // Smoothing cinfo.smoothing_factor = (boolean)options.smooth; // Subsampling switch (options.subsampling) { default: case 0: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 1: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 2: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 3: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; } // Save resolution cinfo.X_density = INCH_TO_POINT(image->xRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.Y_density = INCH_TO_POINT(image->yRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.density_unit = 1; cinfo.write_JFIF_header = (boolean)true; // Start compression jpeg_start_compress(&cinfo, (boolean)true); // Save exif and iptc information if any available if (metaData && !metaData->empty()) { metaData->applyFilters(options.filters); // Save EXIF if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "Exif information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "EXIF information could not be saved."; // TODO: warn the user ? } } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "IPTC information could not be saved."; // TODO: warn the user ? } } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "XMP information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 14, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "XMP information could not be saved."; // TODO: warn the user ? } } } KisPaintDeviceSP dev = new KisPaintDevice(layer->colorSpace()); KoColor c(options.transparencyFillColor, layer->colorSpace()); dev->fill(QRect(0, 0, width, height), c); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), layer->paintDevice(), QRect(0, 0, width, height)); gc.end(); if (options.saveProfile) { const KoColorProfile* colorProfile = layer->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); write_icc_profile(& cinfo, (uchar*) colorProfileData.data(), colorProfileData.size()); } // Write data information JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components]; int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->channelCount(); for (; cinfo.next_scanline < height;) { KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, cinfo.next_scanline, width); quint8 *dst = row_pointer; switch (color_type) { case JCS_GRAYSCALE: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 0);//d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_RGB: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 2); //d[2] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 1); //d[1] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 0); //d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_CMYK: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - cs->scaleToU8(d, 0);//quint8_MAX - d[0] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 1);//quint8_MAX - d[1] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 2);//quint8_MAX - d[2] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 3);//quint8_MAX - d[3] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - d[0]; *(dst++) = quint8_MAX - d[1]; *(dst++) = quint8_MAX - d[2]; *(dst++) = quint8_MAX - d[3]; } while (it->nextPixel()); } break; default: delete [] row_pointer; jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED; } jpeg_write_scanlines(&cinfo, &row_pointer, 1); } // Writing is over jpeg_finish_compress(&cinfo); delete [] row_pointer; // Free memory jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_OK; } void KisJPEGConverter::cancel() { m_d->stop = true; } diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 20dd1a6b9f..79b5976bfa 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,353 +1,353 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } KisImageBuilder_Result KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return KisImageBuilder_RESULT_FAILURE; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) - KoXmlDocument doc = KoXmlDocument(true); + KoXmlDocument doc; bool ok = oldLoadAndParse(m_store, "root", doc); if (ok) ok = loadXML(doc, m_store); if (!ok) { return KisImageBuilder_RESULT_FAILURE; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return KisImageBuilder_RESULT_FAILURE; } if (m_store->hasFile("documentinfo.xml")) { - KoXmlDocument doc = KoXmlDocument(true); + KoXmlDocument doc; if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImageBuilder_Result KraConverter::buildFile(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return KisImageBuilder_RESULT_FAILURE; } bool result = false; m_kraSaver = new KisKraSaver(m_doc); result = saveRootDocuments(m_store); if (!result) { return KisImageBuilder_RESULT_FAILURE; } result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } if (!m_store->finalize()) { return KisImageBuilder_RESULT_FAILURE; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return KisImageBuilder_RESULT_FAILURE; } return KisImageBuilder_RESULT_OK; } bool KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return false; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return false; } bool success = false; if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! success = dev.write(s.data(), s.size()); store->close(); } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) savePreview(store); (void)store->close(); } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); // Success return success; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } bool KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return false; } bool ret = preview.save(&io, "PNG"); io.close(); return ret; } bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4" , filename , errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8))); return false; } dbgUI << "File" << filename << " loaded and parsed"; return true; } bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return false; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return false; } if (!root.hasChildNodes()) { m_doc->setErrorMessage(i18n("The file has no layers.")); return false; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // Legacy from the multi-image .kra file period. for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } return true; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return false; } } } return false; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_image->unblockUpdates(); bool retval = true; if (!m_kraLoader->warningMessages().isEmpty()) { m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); retval = true; } if (retval) { m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); } return retval; } void KraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index d9fac5a1e8..ed0191b979 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1153 +1,1154 @@ /* 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) { - profile = KoColorSpaceRegistry::instance()->profileByName(KoColorSpaceRegistry::instance()->colorSpaceFactory(image->colorSpace()->id())->defaultProfile()); + 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(); if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(image->proofingConfiguration()->proofingModel, image->proofingConfiguration()->proofingDepth, proofingData); if (proofingProfile->valid()){ //if (KoColorSpaceEngineRegistry::instance()->get("icc")) { // KoColorSpaceEngineRegistry::instance()->get("icc")->addProfile(proofingProfile->fileName()); //} 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") && child.childNodesCount() > 0) { + 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); } } 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); bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; layer->setUseInTimeline(timelineEnabled); } 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 = basePath + QDir::separator() + 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; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment 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. 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/pdf/CMakeLists.txt b/plugins/impex/pdf/CMakeLists.txt index 535c0f67fc..d3ada35792 100644 --- a/plugins/impex/pdf/CMakeLists.txt +++ b/plugins/impex/pdf/CMakeLists.txt @@ -1,12 +1,10 @@ -include_directories( ${POPPLER_INCLUDE_DIR} ) - set(kritapdfimport_SOURCES kis_pdf_import.cpp kis_pdf_import_widget.cpp ) ki18n_wrap_ui(kritapdfimport_SOURCES pdfimportwidgetbase.ui ) add_library(kritapdfimport MODULE ${kritapdfimport_SOURCES}) -target_link_libraries(kritapdfimport kritaui ${POPPLER_LIBRARY} ) +target_link_libraries(kritapdfimport kritaui Poppler::Qt5) install(TARGETS kritapdfimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install(PROGRAMS krita_pdf.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/psd/psd_export.cc b/plugins/impex/psd/psd_export.cc index 2448292e4a..e5369ca4b8 100644 --- a/plugins/impex/psd/psd_export.cc +++ b/plugins/impex/psd/psd_export.cc @@ -1,98 +1,98 @@ /* * 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_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "psd_saver.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_psd_export.json", registerPlugin();) psdExport::psdExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } psdExport::~psdExport() { } KisImportExportFilter::ConversionStatus psdExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { PSDSaver psdSaver(document); KisImageBuilder_Result res; if ((res = psdSaver.buildFile(io)) == KisImageBuilder_RESULT_OK) { dbgFile <<"success !"; return KisImportExportFilter::OK; } dbgFile <<" Result =" << res; return KisImportExportFilter::InternalError; } void psdExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("PSDLayerStyleCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); - //addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::SUPPORTED)); + addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::UNSUPPORTED, i18nc("image conversion warning", "Your image contains one or more layers with a color model that is different from the image."))); ImageSizeCheckFactory *factory = dynamic_cast(KisExportCheckRegistry::instance()->get("ImageSizeCheck")); if (factory) { addCapability(factory->create(30000, 30000, KisExportCheckBase::SUPPORTED)); } QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) // << QPair(RGBAColorModelID, Float16BitsColorDepthID) // << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer16BitsColorDepthID) << QPair(LABAColorModelID, Integer8BitsColorDepthID) << QPair(LABAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PSD"); } #include diff --git a/plugins/impex/psd/psd_layer_record.cpp b/plugins/impex/psd/psd_layer_record.cpp index c503f9c855..e04539e7af 100644 --- a/plugins/impex/psd/psd_layer_record.cpp +++ b/plugins/impex/psd/psd_layer_record.cpp @@ -1,776 +1,777 @@ /* * 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); switch(m_header.colormode) { case(Bitmap): case(Indexed): case(DuoTone): case(Grayscale): case(MultiChannel): if (nChannels < 1) { error = QString("Not enough channels. Got: %1").arg(nChannels); return false; } break; case(RGB): case(CMYK): case(Lab): default: if (nChannels < 3) { error = QString("Not enough channels. Got: %1").arg(nChannels); return false; } break; }; 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) { 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); } } } 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/tiff/kis_tiff_converter.cc b/plugins/impex/tiff/kis_tiff_converter.cc index bc91421bbb..9899c1e938 100644 --- a/plugins/impex/tiff/kis_tiff_converter.cc +++ b/plugins/impex/tiff/kis_tiff_converter.cc @@ -1,743 +1,745 @@ /* * Copyright (c) 2005-2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_tiff_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tiff_reader.h" #include "kis_tiff_ycbcr_reader.h" #include "kis_buffer_stream.h" #include "kis_tiff_writer_visitor.h" #if TIFFLIB_VERSION < 20111221 typedef size_t tmsize_t; #endif namespace { QPair getColorSpaceForColorType(uint16 sampletype, uint16 color_type, uint16 color_nb_bits, TIFF *image, uint16 &nbchannels, uint16 &extrasamplescount, uint8 &destDepth) { if (color_type == PHOTOMETRIC_MINISWHITE || color_type == PHOTOMETRIC_MINISBLACK) { if (nbchannels == 0) nbchannels = 1; extrasamplescount = nbchannels - 1; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(GrayAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(GrayAColorModelID.id(), Float32BitsColorDepthID.id()); } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(GrayAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_RGB /*|| color_type == */) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(RGBAColorModelID.id(), Float32BitsColorDepthID.id()); } return QPair(); } else { if (color_nb_bits <= 8) { destDepth = 8; return QPair(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } } } else if (color_type == PHOTOMETRIC_YCBCR) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (color_nb_bits <= 8) { destDepth = 8; return QPair(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_SEPARATED) { if (nbchannels == 0) nbchannels = 4; // SEPARATED is in general CMYK but not always, so we check uint16 inkset; if ((TIFFGetField(image, TIFFTAG_INKSET, &inkset) == 0)) { dbgFile << "Image does not define the inkset."; inkset = 2; } if (inkset != INKSET_CMYK) { dbgFile << "Unsupported inkset (right now, only CMYK is supported)"; char** ink_names; uint16 numberofinks; if (TIFFGetField(image, TIFFTAG_INKNAMES, &ink_names) == 1 && TIFFGetField(image, TIFFTAG_NUMBEROFINKS, &numberofinks) == 1) { dbgFile << "Inks are :"; for (uint i = 0; i < numberofinks; i++) { dbgFile << ink_names[i]; } } else { dbgFile << "inknames are not defined !"; // To be able to read stupid adobe files, if there are no information about inks and four channels, then it's a CMYK file : if (nbchannels - extrasamplescount != 4) { return QPair(); } } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_CIELAB || color_type == PHOTOMETRIC_ICCLAB) { destDepth = 16; if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count return QPair(LABAColorModelID.id(), Integer16BitsColorDepthID.id()); } else if (color_type == PHOTOMETRIC_PALETTE) { destDepth = 16; if (nbchannels == 0) nbchannels = 2; extrasamplescount = nbchannels - 2; // FIX the extrasamples count // <-- we will convert the index image to RGBA16 as the palette is always on 16bits colors return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } return QPair(); } } KisPropertiesConfigurationSP KisTIFFOptions::toProperties() const { QHash compToIndex; compToIndex[COMPRESSION_NONE] = 0; compToIndex[COMPRESSION_JPEG] = 1; compToIndex[COMPRESSION_DEFLATE] = 2; compToIndex[COMPRESSION_LZW] = 3; compToIndex[COMPRESSION_JP2000] = 4; compToIndex[COMPRESSION_CCITTRLE] = 5; compToIndex[COMPRESSION_CCITTFAX3] = 6; compToIndex[COMPRESSION_CCITTFAX4] = 7; compToIndex[COMPRESSION_PIXARLOG] = 8; KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("compressiontype", compToIndex.value(compressionType, 0)); cfg->setProperty("predictor", predictor - 1); cfg->setProperty("alpha", alpha); cfg->setProperty("flatten", flatten); cfg->setProperty("quality", jpegQuality); cfg->setProperty("deflate", deflateCompress); cfg->setProperty("faxmode", faxMode - 1); cfg->setProperty("pixarlog", pixarLogCompress); cfg->setProperty("saveProfile", saveProfile); return cfg; } void KisTIFFOptions::fromProperties(KisPropertiesConfigurationSP cfg) { QHash indexToComp; indexToComp[0] = COMPRESSION_NONE; indexToComp[1] = COMPRESSION_JPEG; indexToComp[2] = COMPRESSION_DEFLATE; indexToComp[3] = COMPRESSION_LZW; indexToComp[4] = COMPRESSION_JP2000; indexToComp[5] = COMPRESSION_CCITTRLE; indexToComp[6] = COMPRESSION_CCITTFAX3; indexToComp[7] = COMPRESSION_CCITTFAX4; indexToComp[8] = COMPRESSION_PIXARLOG; compressionType = indexToComp.value( cfg->getInt("compressiontype", 0), COMPRESSION_NONE); predictor = cfg->getInt("predictor", 0) + 1; alpha = cfg->getBool("alpha", true); flatten = cfg->getBool("flatten", true); jpegQuality = cfg->getInt("quality", 80); deflateCompress = cfg->getInt("deflate", 6); faxMode = cfg->getInt("faxmode", 0) + 1; pixarLogCompress = cfg->getInt("pixarlog", 6); saveProfile = cfg->getBool("saveProfile", true); } KisTIFFConverter::KisTIFFConverter(KisDocument *doc) { m_doc = doc; m_stop = false; TIFFSetWarningHandler(0); TIFFSetErrorHandler(0); } KisTIFFConverter::~KisTIFFConverter() { } KisImageBuilder_Result KisTIFFConverter::decode(const QString &filename) { dbgFile << "Start decoding TIFF File"; // Opent the TIFF file TIFF *image = 0; if ((image = TIFFOpen(QFile::encodeName(filename), "r")) == 0) { dbgFile << "Could not open the file, either it does not exist, either it is not a TIFF :" << filename; return (KisImageBuilder_RESULT_BAD_FETCH); } do { dbgFile << "Read new sub-image"; KisImageBuilder_Result result = readTIFFDirectory(image); if (result != KisImageBuilder_RESULT_OK) { return result; } } while (TIFFReadDirectory(image)); // Freeing memory TIFFClose(image); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisTIFFConverter::readTIFFDirectory(TIFF* image) { // Read information about the tiff uint32 width, height; if (TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width) == 0) { dbgFile << "Image does not define its width"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } if (TIFFGetField(image, TIFFTAG_IMAGELENGTH, &height) == 0) { dbgFile << "Image does not define its height"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } float xres; if (TIFFGetField(image, TIFFTAG_XRESOLUTION, &xres) == 0) { dbgFile << "Image does not define x resolution"; // but we don't stop xres = 100; } float yres; if (TIFFGetField(image, TIFFTAG_YRESOLUTION, &yres) == 0) { dbgFile << "Image does not define y resolution"; // but we don't stop yres = 100; } uint16 depth; if ((TIFFGetField(image, TIFFTAG_BITSPERSAMPLE, &depth) == 0)) { dbgFile << "Image does not define its depth"; depth = 1; } uint16 sampletype; if ((TIFFGetField(image, TIFFTAG_SAMPLEFORMAT, &sampletype) == 0)) { dbgFile << "Image does not define its sample type"; sampletype = SAMPLEFORMAT_UINT; } // Determine the number of channels (useful to know if a file has an alpha or not uint16 nbchannels; if (TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &nbchannels) == 0) { dbgFile << "Image has an undefined number of samples per pixel"; nbchannels = 0; } // Get the number of extrasamples and information about them uint16 *sampleinfo = 0, extrasamplescount; if (TIFFGetField(image, TIFFTAG_EXTRASAMPLES, &extrasamplescount, &sampleinfo) == 0) { extrasamplescount = 0; } // Determine the colorspace uint16 color_type; if (TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &color_type) == 0) { dbgFile << "Image has an undefined photometric interpretation"; color_type = PHOTOMETRIC_MINISWHITE; } uint8 dstDepth = 0; - QPair colorSpaceId = getColorSpaceForColorType(sampletype, color_type, depth, image, nbchannels, extrasamplescount, dstDepth); - if (colorSpaceId.first.isEmpty()) { + QPair colorSpaceIdTag = getColorSpaceForColorType(sampletype, color_type, depth, image, nbchannels, extrasamplescount, dstDepth); + if (colorSpaceIdTag.first.isEmpty()) { dbgFile << "Image has an unsupported colorspace :" << color_type << " for this depth :" << depth; TIFFClose(image); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } - dbgFile << "Colorspace is :" << colorSpaceId.first << colorSpaceId.second << " with a depth of" << depth << " and with a nb of channels of" << nbchannels; + dbgFile << "Colorspace is :" << colorSpaceIdTag.first << colorSpaceIdTag.second << " with a depth of" << depth << " and with a nb of channels of" << nbchannels; // Read image profile dbgFile << "Reading profile"; const KoColorProfile* profile = 0; quint32 EmbedLen; quint8* EmbedBuffer; if (TIFFGetField(image, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer) == 1) { dbgFile << "Profile found"; QByteArray rawdata; rawdata.resize(EmbedLen); memcpy(rawdata.data(), EmbedBuffer, EmbedLen); - profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, rawdata); + profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceIdTag.first, colorSpaceIdTag.second, rawdata); } - // Check that the profile is used by the color space + const QString colorSpaceId = + KoColorSpaceRegistry::instance()->colorSpaceId(colorSpaceIdTag.first, colorSpaceIdTag.second); - if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory(KoColorSpaceRegistry::instance()->colorSpaceId(colorSpaceId.first, colorSpaceId.second))->profileIsCompatible(profile)) { - dbgFile << "The profile " << profile->name() << " is not compatible with the color space model " << colorSpaceId.first << " " << colorSpaceId.second; + // Check that the profile is used by the color space + if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { + dbgFile << "The profile " << profile->name() << " is not compatible with the color space model " << colorSpaceIdTag.first << " " << colorSpaceIdTag.second; profile = 0; } // Do not use the linear gamma profile for 16 bits/channel by default, tiff files are usually created with // gamma correction. XXX: Should we ask the user? - if (!profile && colorSpaceId.first == RGBAColorModelID.id() && colorSpaceId.second == Integer16BitsColorDepthID.id()) { + if (!profile && colorSpaceIdTag.first == RGBAColorModelID.id() && colorSpaceIdTag.second == Integer16BitsColorDepthID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc"); dbgFile << "Getting srgb profile" << profile; } // Retrieve a pointer to the colorspace const KoColorSpace* cs = 0; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; - cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); + cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile); } else { - cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, 0); + cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, 0); } if (cs == 0) { - dbgFile << "Colorspace" << colorSpaceId.first << colorSpaceId.second << " is not available, please check your installation."; + dbgFile << "Colorspace" << colorSpaceIdTag.first << colorSpaceIdTag.second << " is not available, please check your installation."; TIFFClose(image); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { dbgFile << "The profile can't be used in krita, need conversion"; - transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); + transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Check if there is an alpha channel int8 alphapos = -1; // <- no alpha // Check which extra is alpha if any dbgFile << "There are" << nbchannels << " channels and" << extrasamplescount << " extra channels"; if (sampleinfo) { // index images don't have any sampleinfo, and therefor sampleinfo == 0 for (int i = 0; i < extrasamplescount; i ++) { dbgFile << i << "" << extrasamplescount << "" << (cs->colorChannelCount()) << nbchannels << "" << sampleinfo[i]; if (sampleinfo[i] == EXTRASAMPLE_ASSOCALPHA) { // XXX: dangelo: the color values are already multiplied with // the alpha value. This needs to be reversed later (postprocessor?) alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_UNASSALPHA) { // color values are not premultiplied with alpha, and can be used as they are. alphapos = i; } } } dbgFile << "Alpha pos:" << alphapos; // Read META Information KoDocumentInfo * info = m_doc->documentInfo(); char* text; if (TIFFGetField(image, TIFFTAG_ARTIST, &text) == 1) { info->setAuthorInfo("creator", text); } if (TIFFGetField(image, TIFFTAG_DOCUMENTNAME, &text) == 1) { info->setAboutInfo("title", text); } if (TIFFGetField(image, TIFFTAG_IMAGEDESCRIPTION, &text) == 1) { info->setAboutInfo("description", text); } // Get the planar configuration uint16 planarconfig; if (TIFFGetField(image, TIFFTAG_PLANARCONFIG, &planarconfig) == 0) { dbgFile << "Plannar configuration is not define"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } // Creating the KisImageSP if (! m_image) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); m_image->setResolution( POINT_TO_INCH(xres), POINT_TO_INCH(yres )); // It is the "invert" macro because we convert from pointer-per-inchs to points Q_CHECK_PTR(m_image); } else { if (m_image->width() < (qint32)width || m_image->height() < (qint32)height) { quint32 newwidth = (m_image->width() < (qint32)width) ? width : m_image->width(); quint32 newheight = (m_image->height() < (qint32)height) ? height : m_image->height(); m_image->resizeImage(QRect(0,0,newwidth, newheight)); } } KisPaintLayer* layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), quint8_MAX); tdata_t buf = 0; tdata_t* ps_buf = 0; // used only for planar configuration separated KisBufferStreamBase* tiffstream; KisTIFFReaderBase* tiffReader = 0; quint8 poses[5]; KisTIFFPostProcessor* postprocessor = 0; // Configure poses uint8 nbcolorsamples = nbchannels - extrasamplescount; switch (color_type) { case PHOTOMETRIC_MINISWHITE: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessorInvert(nbcolorsamples); } break; case PHOTOMETRIC_MINISBLACK: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_CIELAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessorCIELABtoICCLAB(nbcolorsamples); } break; case PHOTOMETRIC_ICCLAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_RGB: { if (sampletype == SAMPLEFORMAT_IEEEFP) { poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3; } else { poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3; } postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_SEPARATED: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; poses[4] = 4; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; default: break; } // Initisalize tiffReader uint16 * lineSizeCoeffs = new uint16[nbchannels]; uint16 vsubsampling = 1; uint16 hsubsampling = 1; for (uint i = 0; i < nbchannels; i++) { lineSizeCoeffs[i] = 1; } if (color_type == PHOTOMETRIC_PALETTE) { uint16 *red; // No need to free them they are free by libtiff uint16 *green; uint16 *blue; if ((TIFFGetField(image, TIFFTAG_COLORMAP, &red, &green, &blue)) == 0) { dbgFile << "Indexed image does not define a palette"; TIFFClose(image); delete [] lineSizeCoeffs; return KisImageBuilder_RESULT_INVALID_ARG; } tiffReader = new KisTIFFReaderFromPalette(layer->paintDevice(), red, green, blue, poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (color_type == PHOTOMETRIC_YCBCR) { TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRSUBSAMPLING, &hsubsampling, &vsubsampling); lineSizeCoeffs[1] = hsubsampling; lineSizeCoeffs[2] = hsubsampling; uint16 position; TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRPOSITIONING, &position); if (dstDepth == 8) { tiffReader = new KisTIFFYCbCrReaderTarget8Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling, (KisTIFFYCbCr::Position)position); } else if (dstDepth == 16) { tiffReader = new KisTIFFYCbCrReaderTarget16Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling, (KisTIFFYCbCr::Position)position); } } else if (dstDepth == 8) { tiffReader = new KisTIFFReaderTarget8bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (dstDepth == 16) { uint16 alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue = 15360; // representation of 1.0 in half } else { alphaValue = quint16_MAX; } tiffReader = new KisTIFFReaderTarget16bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue); } else if (dstDepth == 32) { union { float f; uint32 i; } alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue.f = 1.0f; } else { alphaValue.i = quint32_MAX; } tiffReader = new KisTIFFReaderTarget32bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue.i); } if (!tiffReader) { delete postprocessor; delete[] lineSizeCoeffs; TIFFClose(image); dbgFile << "Image has an invalid/unsupported color type: " << color_type; return KisImageBuilder_RESULT_INVALID_ARG; } if (TIFFIsTiled(image)) { dbgFile << "tiled image"; uint32 tileWidth, tileHeight; uint32 x, y; TIFFGetField(image, TIFFTAG_TILEWIDTH, &tileWidth); TIFFGetField(image, TIFFTAG_TILELENGTH, &tileHeight); uint32 linewidth = (tileWidth * depth * nbchannels) / 8; if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(TIFFTileSize(image)); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, linewidth); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, linewidth); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, linewidth); } } else { ps_buf = new tdata_t[nbchannels]; uint32 * lineSizes = new uint32[nbchannels]; tmsize_t baseSize = TIFFTileSize(image) / nbchannels; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(baseSize); lineSizes[i] = tileWidth; // baseSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << linewidth << "" << nbchannels << "" << layer->paintDevice()->colorSpace()->colorChannelCount(); for (y = 0; y < height; y += tileHeight) { for (x = 0; x < width; x += tileWidth) { dbgFile << "Reading tile x =" << x << " y =" << y; if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadTile(image, buf, x, y, 0, (tsample_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadTile(image, ps_buf[i], x, y, 0, i); } } uint32 realTileWidth = (x + tileWidth) < width ? tileWidth : width - x; for (uint yintile = 0; y + yintile < height && yintile < tileHeight / vsubsampling;) { tiffReader->copyDataToChannels(x, y + yintile , realTileWidth, tiffstream); yintile += 1; tiffstream->moveToLine(yintile); } tiffstream->restart(); } } } else { dbgFile << "striped image"; tsize_t stripsize = TIFFStripSize(image); uint32 rowsPerStrip; TIFFGetFieldDefaulted(image, TIFFTAG_ROWSPERSTRIP, &rowsPerStrip); dbgFile << rowsPerStrip << "" << height; rowsPerStrip = qMin(rowsPerStrip, height); // when TIFFNumberOfStrips(image) == 1 it might happen that rowsPerStrip is incorrectly set if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(stripsize); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, stripsize / rowsPerStrip); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, stripsize / rowsPerStrip); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, stripsize / rowsPerStrip); } } else { ps_buf = new tdata_t[nbchannels]; uint32 scanLineSize = stripsize / rowsPerStrip; dbgFile << " scanLineSize for each plan =" << scanLineSize; uint32 * lineSizes = new uint32[nbchannels]; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(stripsize); lineSizes[i] = scanLineSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << "Scanline size =" << TIFFRasterScanlineSize(image) << " / strip size =" << TIFFStripSize(image) << " / rowsPerStrip =" << rowsPerStrip << " stripsize/rowsPerStrip =" << stripsize / rowsPerStrip; uint32 y = 0; dbgFile << " NbOfStrips =" << TIFFNumberOfStrips(image) << " rowsPerStrip =" << rowsPerStrip << " stripsize =" << stripsize; for (uint32 strip = 0; y < height; strip++) { if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, 0) , buf, (tsize_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, i), ps_buf[i], (tsize_t) - 1); } } for (uint32 yinstrip = 0 ; yinstrip < rowsPerStrip && y < height ;) { uint linesread = tiffReader->copyDataToChannels(0, y, width, tiffstream); y += linesread; yinstrip += linesread; tiffstream->moveToLine(yinstrip); } tiffstream->restart(); } } tiffReader->finalize(); delete[] lineSizeCoeffs; delete tiffReader; delete tiffstream; if (planarconfig == PLANARCONFIG_CONTIG) { _TIFFfree(buf); } else { for (uint i = 0; i < nbchannels; i++) { _TIFFfree(ps_buf[i]); } delete[] ps_buf; } m_image->addNode(KisNodeSP(layer), m_image->rootLayer().data()); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisTIFFConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP KisTIFFConverter::image() { return m_image; } KisImageBuilder_Result KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options) { dbgFile << "Start writing TIFF File"; if (!kisimage) return KisImageBuilder_RESULT_EMPTY; // Open file for writing TIFF *image; if ((image = TIFFOpen(QFile::encodeName(filename), "w")) == 0) { dbgFile << "Could not open the file for writing" << filename; TIFFClose(image); return (KisImageBuilder_RESULT_FAILURE); } // Set the document information KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty()) { TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData()); } QString abstract = info->aboutInfo("description"); if (!abstract.isEmpty()) { TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData()); } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData()); } dbgFile << "xres: " << INCH_TO_POINT(kisimage->xRes()) << " yres: " << INCH_TO_POINT(kisimage->yRes()); TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes())); // It is the "invert" macro because we convert from pointer-per-inchs to points TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes())); KisGroupLayer* root = dynamic_cast(kisimage->rootLayer().data()); if (root == 0) { TIFFClose(image); return KisImageBuilder_RESULT_FAILURE; } KisTIFFWriterVisitor* visitor = new KisTIFFWriterVisitor(image, &options); if (!visitor->visit(root)) { TIFFClose(image); return KisImageBuilder_RESULT_FAILURE; } TIFFClose(image); return KisImageBuilder_RESULT_OK; } void KisTIFFConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/pixels.c b/plugins/impex/xcf/3rdparty/xcftools/pixels.c index 4247d94da9..d7037937c9 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/pixels.c +++ b/plugins/impex/xcf/3rdparty/xcftools/pixels.c @@ -1,490 +1,491 @@ /* Pixel and tile functions for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. - * + * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #define DEBUG #include "xcftools.h" #include "pixels.h" #include #include rgba colormap[256] ; unsigned colormapLength=0 ; int degrayPixel(rgba pixel) { if( ((pixel >> RED_SHIFT) & 255) == ((pixel >> GREEN_SHIFT) & 255) && ((pixel >> RED_SHIFT) & 255) == ((pixel >> BLUE_SHIFT) & 255) ) return (pixel >> RED_SHIFT) & 255 ; return -1 ; } /* ****************************************************************** */ typedef const struct _convertParams { int bpp ; int shift[4] ; uint32_t base_pixel ; const rgba *lookup ; } convertParams ; #define RGB_SHIFT RED_SHIFT, GREEN_SHIFT, BLUE_SHIFT #define OPAQUE (255 << ALPHA_SHIFT) static convertParams convertRGB = { 3, {RGB_SHIFT}, OPAQUE, 0 }; static convertParams convertRGBA = { 4, {RGB_SHIFT, ALPHA_SHIFT}, 0,0 }; static convertParams convertGRAY = { 1, {-1}, OPAQUE, graytable }; static convertParams convertGRAYA = { 2, {-1,ALPHA_SHIFT}, 0, graytable }; static convertParams convertINDEXED = { 1, {-1}, OPAQUE, colormap }; static convertParams convertINDEXEDA = { 2, {-1,ALPHA_SHIFT}, 0, colormap }; static convertParams convertColormap = { 3, {RGB_SHIFT}, 0, 0 }; static convertParams convertChannel = { 1, {ALPHA_SHIFT}, 0, 0 }; /* ****************************************************************** */ static int tileDirectoryOneLevel(struct tileDimensions *dim,uint32_t ptr) { if( ptr == 0 ) return 0 ; if( xcfL(ptr ) != dim->c.r - dim->c.l || xcfL(ptr+4) != dim->c.b - dim->c.t ) FatalBadXCF("Drawable size mismatch at %" PRIX32, ptr); return ptr += 8 ; } static void initTileDirectory(struct tileDimensions *dim,struct xcfTiles *tiles, const char *type) { uint32_t ptr ; uint32_t data ; ptr = tiles->hierarchy ; tiles->hierarchy = 0 ; if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; if( tiles->params == &convertChannel ) { /* A layer mask is a channel. * Skip a name and a property list. */ xcfString(ptr,&ptr); while( xcfNextprop(&ptr,&data) != PROP_END ) ; ptr = xcfOffset(ptr,4*4); if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; } /* The XCF format has a dummy "hierarchy" level which was * once meant to mean something, but never happened. It contains * the bpp value and a list of "level" pointers; but only the * first level actually contains data. */ data = xcfL(ptr) ; if( xcfL(ptr) != tiles->params->bpp ) FatalBadXCF("%"PRIu32" bytes per pixel for %s drawable",xcfL(ptr),type); ptr = xcfOffset(ptr+4,3*4) ; if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; xcfCheckspace(ptr,dim->ntiles*4+4,"Tile directory at %" PRIX32,ptr); - if( xcfL(ptr + dim->ntiles*4) != 0 ) - FatalBadXCF("Wrong sized tile directory at %" PRIX32,ptr); +/* if( xcfL(ptr + dim->ntiles*4) != 0 ) + FatalBadXCF("Wrong sized tile directory at %" PRIX32,ptr);*/ + #define REUSE_RAW_DATA tiles->tileptrs = (uint32_t*)(xcf_file + ptr) #if defined(WORDS_BIGENDIAN) && defined(CAN_DO_UNALIGNED_WORDS) REUSE_RAW_DATA; #else # if defined(WORDS_BIGENDIAN) if( (ptr&3) == 0 ) REUSE_RAW_DATA; else # endif { unsigned i ; tiles->tileptrs = xcfmalloc(dim->ntiles * sizeof(uint32_t)) ; for( i = 0 ; i < dim->ntiles ; i++ ) tiles->tileptrs[i] = xcfL(ptr+i*4); } #endif } void initLayer(struct xcfLayer *layer) { if( layer->dim.ntiles == 0 || (layer->pixels.hierarchy == 0 && layer->mask.hierarchy == 0) ) return ; switch(layer->type) { #define DEF(X) case GIMP_##X##_IMAGE: layer->pixels.params = &convert##X; break DEF(RGB); DEF(RGBA); DEF(GRAY); DEF(GRAYA); DEF(INDEXED); DEF(INDEXEDA); default: FatalUnsupportedXCF(_("Layer type %s"),_(showGimpImageType(layer->type))); } initTileDirectory(&layer->dim,&layer->pixels, _(showGimpImageType(layer->type))); layer->mask.params = &convertChannel ; initTileDirectory(&layer->dim,&layer->mask,"layer mask"); } static void copyStraightPixels(rgba *dest,unsigned npixels, uint32_t ptr,convertParams *params); void initColormap(void) { uint32_t ncolors ; if( XCF.colormapptr == 0 ) { colormapLength = 0 ; return ; } ncolors = xcfL(XCF.colormapptr) ; if( ncolors > 256 ) FatalUnsupportedXCF(_("Color map has more than 256 entries")); copyStraightPixels(colormap,ncolors,XCF.colormapptr+4,&convertColormap); colormapLength = ncolors ; #ifdef xDEBUG { unsigned j ; fprintf(stderr,"Colormap decoding OK\n"); for( j = 0 ; j < ncolors ; j++ ) { if( j % 8 == 0 ) fprintf(stderr,"\n"); fprintf(stderr," %08x",colormap[j]); } fprintf(stderr,"\n"); } #endif } /* ****************************************************************** */ struct Tile * newTile(struct rect r) { unsigned npixels = (unsigned)(r.b-r.t) * (unsigned)(r.r-r.l) ; struct Tile *data = xcfmalloc(sizeof(struct Tile) - sizeof(rgba)*(TILE_HEIGHT*TILE_WIDTH - npixels)) ; data->count = npixels ; data->refcount = 1 ; data->summary = 0 ; return data ; } struct Tile * forkTile(struct Tile* tile) { if( ++tile->refcount <= 0 ) FatalUnsupportedXCF(_("Unbelievably many layers?\n" "More likely to be a bug in %s"),progname); return tile ; } void freeTile(struct Tile* tile) { if( --tile->refcount == 0 ) xcffree(tile) ; } summary_t tileSummary(struct Tile *tile) { unsigned i ; summary_t summary ; if( (tile->summary & TILESUMMARY_UPTODATE) != 0 ) return tile->summary ; summary = TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i=0; summary && icount; i++ ) { if( FULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLNULL ; else if( NULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLFULL ; else summary = 0 ; } summary += TILESUMMARY_UPTODATE ; tile->summary = summary ; return summary ; } - + void fillTile(struct Tile *tile,rgba data) { unsigned i ; for( i = 0 ; i < tile->count ; i++ ) tile->pixels[i] = data ; if( FULLALPHA(data) ) tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP; else if (NULLALPHA(data) ) tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLNULL+TILESUMMARY_CRISP; else tile->summary = TILESUMMARY_UPTODATE ; } /* ****************************************************************** */ static void copyStraightPixels(rgba *dest,unsigned npixels, uint32_t ptr,convertParams *params) { unsigned bpp = params->bpp; const rgba *lookup = params->lookup; rgba base_pixel = params->base_pixel ; uint8_t *bp = xcf_file + ptr ; xcfCheckspace(ptr,bpp*npixels, "pixel array (%u x %d bpp) at %"PRIX32,npixels,bpp,ptr); while( npixels-- ) { rgba pixel = base_pixel ; unsigned i ; for( i = 0 ; i < bpp ; i++ ) { if( params->shift[i] < 0 ) { pixel += lookup[*bp++] ; } else { pixel += *bp++ << params->shift[i] ; } } *dest++ = pixel ; } } static void copyRLEpixels(rgba *dest,unsigned npixels,uint32_t ptr,convertParams *params) { unsigned i,j ; rgba base_pixel = params->base_pixel ; #ifdef xDEBUG fprintf(stderr,"RLE stream at %x, want %u x %u pixels, base %x\n", ptr,params->bpp,npixels,base_pixel); #endif - + /* This algorithm depends on the indexed byte always being the first one */ if( params->shift[0] < -1 ) base_pixel = 0 ; for( j = npixels ; j-- ; ) dest[j] = base_pixel ; for( i = 0 ; i < params->bpp ; i++ ) { int shift = params->shift[i] ; if( shift < 0 ) shift = 0 ; for( j = 0 ; j < npixels ; ) { int countspec ; unsigned count ; xcfCheckspace(ptr,2,"RLE data stream"); countspec = (int8_t) xcf_file[ptr++] ; count = countspec >= 0 ? countspec+1 : -countspec ; if( count == 128 ) { xcfCheckspace(ptr,3,"RLE long count"); count = xcf_file[ptr++] << 8 ; count += xcf_file[ptr++] ; } if( j + count > npixels ) FatalBadXCF("Overlong RLE run at %"PRIX32" (plane %u, %u left)", ptr,i,npixels-j); if( countspec >= 0 ) { rgba data = (uint32_t) xcf_file[ptr++] << shift ; while( count-- ) dest[j++] += data ; } else { while( count-- ) dest[j++] += (uint32_t) xcf_file[ptr++] << shift ; } } if( i == 0 && params->shift[0] < 0 ) { const rgba *lookup = params->lookup ; base_pixel = params->base_pixel ; for( j = npixels ; j-- ; ) { dest[j] = lookup[dest[j]-base_pixel] + base_pixel ; } } } #ifdef xDEBUG fprintf(stderr,"RLE decoding OK at %"PRIX32"\n",ptr); /* for( j = 0 ; j < npixels ; j++ ) { if( j % 8 == 0 ) fprintf(stderr,"\n"); fprintf(stderr," %8x",dest[j]); } fprintf(stderr,"\n"); */ #endif } static void copyTilePixels(struct Tile *dest, uint32_t ptr,convertParams *params) { if( FULLALPHA(params->base_pixel) ) dest->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP; else dest->summary = 0 ; switch( XCF.compression ) { case COMPRESS_NONE: copyStraightPixels(dest->pixels,dest->count,ptr,params); break ; case COMPRESS_RLE: copyRLEpixels(dest->pixels,dest->count,ptr,params); break ; default: FatalUnsupportedXCF(_("%s compression"), _(showXcfCompressionType(XCF.compression))); } } struct Tile * getMaskOrLayerTile(struct tileDimensions *dim, struct xcfTiles *tiles, struct rect want) { struct Tile *tile = newTile(want); assert( want.l < want.r && want.t < want.b ); if( tiles->tileptrs == 0 ) { fillTile(tile,0); return tile ; } - + #ifdef xDEBUG fprintf(stderr,"getMaskOrLayer: (%d-%d),(%d-%d)\n",left,right,top,bottom); #endif - + if( isSubrect(want,dim->c) && (want.l - dim->c.l) % TILE_WIDTH == 0 && (want.t - dim->c.t) % TILE_HEIGHT == 0 ) { int tx = TILE_NUM(want.l - dim->c.l); int ty = TILE_NUM(want.t - dim->c.t); if( want.r == TILEXn(*dim,tx+1) && want.b == TILEYn(*dim,ty+1) ) { /* The common case? An entire single tile from the layer */ copyTilePixels(tile,tiles->tileptrs[tx + ty*dim->tilesx],tiles->params); return tile ; } } /* OK, we must construct the wanted tile as a jigsaw */ { unsigned width = want.r-want.l ; rgba *pixvert = tile->pixels ; rgba *pixhoriz ; int y, ty, l0, l1 ; int x, tx, c0, c1 ; unsigned lstart, lnum ; unsigned cstart, cnum ; - + if( !isSubrect(want,dim->c) ) { if( want.l < dim->c.l ) pixvert += (dim->c.l - want.l), want.l = dim->c.l ; if( want.r > dim->c.r ) want.r = dim->c.r ; if( want.t < dim->c.t ) pixvert += (dim->c.t - want.t) * width, want.t = dim->c.t ; if( want.b > dim->c.b ) want.b = dim->c.b ; fillTile(tile,0); } else { tile->summary = -1 ; /* I.e. whatever the jigsaw pieces say */ } #ifdef xDEBUG fprintf(stderr,"jig0 (%d-%d),(%d-%d)\n",left,right,top,bottom); #endif for( y=want.t, ty=TILE_NUM(want.t-dim->c.t), l0=TILEYn(*dim,ty); y want.b ? want.b : l1) - y ; - + pixhoriz = pixvert ; for( x=want.l, tx=TILE_NUM(want.l-dim->c.l), c0=TILEXn(*dim,tx); x want.r ? want.r : c1) - x ; { static struct Tile tmptile ; unsigned dwidth = c1-c0 ; unsigned i, j ; tmptile.count = (c1-c0)*(l1-l0) ; #ifdef xDEBUG fprintf(stderr,"jig ty=%u(%u-%u-%u)(%u+%u) tx=%u(%u-%u-%u)(%u+%u)\n", ty,l0,y,l1,lstart,lnum, tx,c0,x,c1,cstart,cnum); #endif copyTilePixels(&tmptile, tiles->tileptrs[tx+ty*dim->tilesx],tiles->params); for(i=0; isummary &= tmptile.summary ; } } } } return tile ; } void applyMask(struct Tile *tile, struct Tile *mask) { unsigned i ; assertTileCompatibility(tile,mask); assert( tile->count == mask->count ); INIT_SCALETABLE_IF(1); invalidateSummary(tile,0); for( i=0; i < tile->count ;i++ ) tile->pixels[i] = NEWALPHA(tile->pixels[i], scaletable[mask->pixels[i]>>ALPHA_SHIFT] [ALPHA(tile->pixels[i])]); freeTile(mask); } struct Tile * getLayerTile(struct xcfLayer *layer,const struct rect *where) { struct Tile *data ; #ifdef xDEBUG fprintf(stderr,"getLayerTile(%s): (%d-%d),(%d-%d)\n", layer->name,where->l,where->r,where->t,where->b); #endif if( disjointRects(*where,layer->dim.c) || layer->opacity == 0 ) { data = newTile(*where); fillTile(data,0); return data ; } - + data = getMaskOrLayerTile(&layer->dim,&layer->pixels,*where); if( (data->summary & TILESUMMARY_ALLNULL) != 0 ) return data ; if( layer->hasMask ) { struct Tile *mask = getMaskOrLayerTile(&layer->dim,&layer->mask,*where); applyMask(data,mask); } if( layer->opacity < 255 ) { const uint8_t *ourtable ; int i ; invalidateSummary(data,~(TILESUMMARY_CRISP | TILESUMMARY_ALLFULL)); INIT_SCALETABLE_IF(1); ourtable = scaletable[layer->opacity] ; for( i=0; i < data->count; i++ ) data->pixels[i] = NEWALPHA(data->pixels[i],ourtable[ALPHA(data->pixels[i])]) ; } return data ; } - + diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp index b0c1759541..319a11f8b7 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp @@ -1,283 +1,289 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_duplicateop.h" #include "kis_duplicateop_p.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 "kis_duplicateop_settings.h" #include "kis_duplicateop_settings_widget.h" #include "kis_duplicateop_option.h" KisDuplicateOp::KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_image(image) , m_node(node) , m_settings(static_cast(const_cast(settings.data()))) { Q_ASSERT(settings); Q_ASSERT(painter); m_sizeOption.readOptionSetting(settings); + m_rotationOption.readOptionSetting(settings); + m_sizeOption.resetAllSensors(); + m_rotationOption.resetAllSensors(); + m_healing = settings->getBool(DUPLICATE_HEALING); m_perspectiveCorrection = settings->getBool(DUPLICATE_CORRECT_PERSPECTIVE); m_moveSourcePoint = settings->getBool(DUPLICATE_MOVE_SOURCE_POINT); m_cloneFromProjection = settings->getBool(DUPLICATE_CLONE_FROM_PROJECTION); m_srcdev = source()->createCompositionSourceDevice(); } KisDuplicateOp::~KisDuplicateOp() { } #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) KisSpacingInformation KisDuplicateOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); if (!m_duplicateStartIsSet) { m_duplicateStartIsSet = true; m_duplicateStart = info.pos(); } KisPaintDeviceSP realSourceDevice; if (m_cloneFromProjection && m_image) { realSourceDevice = m_image->projection(); } else { KisNodeSP externalSourceNode = m_settings->sourceNode(); /** * The saved layer might have been deleted by then, so check if it * still belongs to a graph */ if (!externalSourceNode || !externalSourceNode->graphListener()) { externalSourceNode = m_node; } realSourceDevice = externalSourceNode->projection(); } + qreal rotation = m_rotationOption.apply(info); + qreal scale = m_sizeOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); - KisDabShape shape(scale, 1.0, 0.0); + KisDabShape shape(scale, 1.0, rotation); static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); if (dstRect.isEmpty()) return KisSpacingInformation(1.0); QPoint srcPoint; if (m_moveSourcePoint) { srcPoint = (dstRect.topLeft() - m_settings->offset()).toPoint(); } else { QPointF hotSpot = brush->hotSpot(shape, info); srcPoint = (m_settings->position() - hotSpot).toPoint(); } qint32 sw = dstRect.width(); qint32 sh = dstRect.height(); // Perspective correction ? // if (m_perspectiveCorrection && m_image && m_image->perspectiveGrid()->countSubGrids() == 1) { // Matrix3qreal startM = Matrix3qreal::Identity(); // Matrix3qreal endM = Matrix3qreal::Identity(); // // First look for the grid corresponding to the start point // KisSubPerspectiveGrid* subGridStart = *m_image->perspectiveGrid()->begin(); // QRect r = QRect(0, 0, m_image->width(), m_image->height()); // if (subGridStart) { // startM = KisPerspectiveMath::computeMatrixTransfoFromPerspective(r, *subGridStart->topLeft(), *subGridStart->topRight(), *subGridStart->bottomLeft(), *subGridStart->bottomRight()); // } // // Second look for the grid corresponding to the end point // KisSubPerspectiveGrid* subGridEnd = *m_image->perspectiveGrid()->begin(); // if (subGridEnd) { // endM = KisPerspectiveMath::computeMatrixTransfoToPerspective(*subGridEnd->topLeft(), *subGridEnd->topRight(), *subGridEnd->bottomLeft(), *subGridEnd->bottomRight(), r); // } // // Compute the translation in the perspective transformation space: // QPointF positionStartPaintingT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart)); // QPointF duplicateStartPositionT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart) - QPointF(m_settings->offset())); // QPointF translat = duplicateStartPositionT - positionStartPaintingT; // KisSequentialIterator dstIt(m_srcdev, QRect(0, 0, sw, sh)); // KisRandomSubAccessorSP srcAcc = realSourceDevice->createRandomSubAccessor(); // //Action // do { // QPointF p = KisPerspectiveMath::matProd(startM, KisPerspectiveMath::matProd(endM, QPointF(dstIt.x() + dstRect.x(), dstIt.y() + dstRect.y())) + translat); // srcAcc->moveTo(p); // srcAcc->sampledOldRawData(dstIt.rawData()); // } while (dstIt.nextPixel()); // } // else { KisPainter copyPainter(m_srcdev); copyPainter.setCompositeOp(COMPOSITE_COPY); copyPainter.bitBltOldData(0, 0, realSourceDevice, srcPoint.x(), srcPoint.y(), sw, sh); copyPainter.end(); } // heal ? if (m_healing) { QRect healRect(dstRect); const bool smallWidth = healRect.width() < 3; const bool smallHeight = healRect.height() < 3; if (smallWidth || smallHeight) { healRect.adjust(-smallWidth, -smallHeight, smallWidth, smallHeight); } const int healSW = healRect.width(); const int healSH = healRect.height(); quint16 srcData[4]; quint16 tmpData[4]; QScopedArrayPointer matrix(new qreal[ 3 * healSW * healSH ]); // First divide const KoColorSpace* srcCs = realSourceDevice->colorSpace(); const KoColorSpace* tmpCs = m_srcdev->colorSpace(); KisHLineConstIteratorSP srcIt = realSourceDevice->createHLineConstIteratorNG(healRect.x(), healRect.y() , healSW); KisHLineIteratorSP tmpIt = m_srcdev->createHLineIteratorNG(0, 0, healSW); qreal* matrixIt = matrix.data(); for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { srcCs->toLabA16(srcIt->oldRawData(), (quint8*)srcData, 1); tmpCs->toLabA16(tmpIt->rawData(), (quint8*)tmpData, 1); // Division for (int k = 0; k < 3; k++) { matrixIt[k] = srcData[k] / (qreal)qMax((int)tmpData [k], 1); } srcIt->nextPixel(); tmpIt->nextPixel(); matrixIt += 3; } srcIt->nextRow(); tmpIt->nextRow(); } // Minimize energy { int iter = 0; qreal err; QScopedArrayPointer solution(new qreal[ 3 * healSW * healSH ]); do { err = DuplicateOpUtils::minimizeEnergy(matrix.data(), solution.data(), healSW, healSH); solution.swap(matrix); iter++; } while (err > 0.00001 && iter < 100); } // Finally multiply KisHLineIteratorSP tmpIt2 = m_srcdev->createHLineIteratorNG(0, 0, healSW); matrixIt = &matrix[0]; for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { tmpCs->toLabA16(tmpIt2->rawData(), (quint8*)tmpData, 1); // Multiplication for (int k = 0; k < 3; k++) { tmpData[k] = (int)CLAMP(matrixIt[k] * qMax((int) tmpData[k], 1), 0, 65535); } tmpCs->fromLabA16((quint8*)tmpData, tmpIt2->rawData(), 1); tmpIt2->nextPixel(); matrixIt += 3; } tmpIt2->nextRow(); } } painter()->bitBltWithFixedSelection(dstRect.x(), dstRect.y(), m_srcdev, dab, dstRect.width(), dstRect.height()); painter()->renderMirrorMaskSafe(dstRect, m_srcdev, 0, 0, dab, !m_dabCache->needSeparateOriginal()); return effectiveSpacing(scale); } diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h index 2c626ec976..19c061dbaa 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h @@ -1,75 +1,77 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DUPLICATEOP_H_ #define KIS_DUPLICATEOP_H_ #include "kis_brush_based_paintop.h" #include #include #include #include #include +#include #include "kis_duplicateop_settings.h" class KisPaintInformation; class QPointF; class KisPainter; class KisDuplicateOp : public KisBrushBasedPaintOp { public: KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisDuplicateOp() override; KisSpacingInformation paintAt(const KisPaintInformation& info) override; private: qreal minimizeEnergy(const qreal* m, qreal* sol, int w, int h); private: KisImageSP m_image; KisNodeSP m_node; KisDuplicateOpSettingsSP m_settings; KisPaintDeviceSP m_srcdev; KisPaintDeviceSP m_target; QPointF m_duplicateStart; bool m_duplicateStartIsSet; KisPressureSizeOption m_sizeOption; + KisPressureRotationOption m_rotationOption; bool m_healing; bool m_perspectiveCorrection; bool m_moveSourcePoint; bool m_cloneFromProjection; }; #endif // KIS_DUPLICATEOP_H_ diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings_widget.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings_widget.cpp index 342be01e21..ca768c2957 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings_widget.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings_widget.cpp @@ -1,75 +1,77 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_duplicateop_settings_widget.h" #include "kis_duplicateop_settings.h" #include "kis_duplicateop_option.h" #include #include #include #include #include +#include #include #include #include "kis_texture_option.h" #include "kis_curve_option_widget.h" #include #include "kis_pressure_texture_strength_option.h" #include KisDuplicateOpSettingsWidget::KisDuplicateOpSettingsWidget(QWidget* parent) : KisBrushBasedPaintopOptionWidget(parent) { setObjectName("brush option widget"); setPrecisionEnabled(true); addPaintOpOption(new KisCompositeOpOption(true), i18n("Blending Mode")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureOpacityOption(), i18n("Transparent"), i18n("Opaque")), i18n("Opacity")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureSizeOption(), i18n("0%"), i18n("100%")), i18n("Size")); + addPaintOpOption(new KisCurveOptionWidget(new KisPressureRotationOption(), i18n("-180°"), i18n("180°")), i18n("Rotation")); addPaintOpOption(new KisPressureMirrorOptionWidget(), i18n("Mirror")); addPaintOpOption(new KisDuplicateOpOption(), i18n("Painting Mode")); addPaintOpOption(new KisTextureOption(), i18n("Pattern")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureTextureStrengthOption(), i18n("Weak"), i18n("Strong")), i18n("Strength")); } KisDuplicateOpSettingsWidget::~KisDuplicateOpSettingsWidget() { } KisPropertiesConfigurationSP KisDuplicateOpSettingsWidget::configuration() const { KisDuplicateOpSettings *config = new KisDuplicateOpSettings(); config->setOptionsWidget(const_cast(this)); config->setProperty("paintop", "duplicate"); // XXX: make this a const id string writeConfiguration(config); return config; } KisPaintopLodLimitations KisDuplicateOpSettingsWidget::lodLimitations() const { KisPaintopLodLimitations l = KisBrushBasedPaintopOptionWidget::lodLimitations(); l.blockers << KoID("clone-brush", i18nc("PaintOp instant preview limitation", "Clone Brush (temporarily disabled)")); return l; } diff --git a/plugins/tools/basictools/kis_tool_brush.cc b/plugins/tools/basictools/kis_tool_brush.cc index 1ab772f75e..cac06cc1b4 100644 --- a/plugins/tools/basictools/kis_tool_brush.cc +++ b/plugins/tools/basictools/kis_tool_brush.cc @@ -1,468 +1,469 @@ /* * kis_tool_brush.cc - part of Krita * * Copyright (c) 2003-2004 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_brush.h" #include #include #include #include #include #include #include #include #include #include "kis_cursor.h" #include "kis_config.h" #include "kis_slider_spin_box.h" #include "kundo2magicstring.h" #define MAXIMUM_SMOOTHNESS_DISTANCE 1000.0 // 0..1000.0 == weight in gui #define MAXIMUM_MAGNETISM 1000 void KisToolBrush::addSmoothingAction(int enumId, const QString &id, const QString &name, const QIcon &icon, KActionCollection *globalCollection) { /** * KisToolBrush is the base of several tools, but the actions * should be unique, so let's be careful with them */ if (!globalCollection->action(id)) { QAction *action = new QAction(name, globalCollection); action->setIcon(icon); globalCollection->addAction(id, action); } QAction *action = dynamic_cast(globalCollection->action(id)); addAction(id, action); connect(action, SIGNAL(triggered()), &m_signalMapper, SLOT(map())); m_signalMapper.setMapping(action, enumId); } KisToolBrush::KisToolBrush(KoCanvasBase * canvas) : KisToolFreehand(canvas, KisCursor::load("tool_freehand_cursor.png", 5, 5), kundo2_i18n("Freehand Brush Stroke")) { setObjectName("tool_brush"); connect(this, SIGNAL(smoothingTypeChanged()), this, SLOT(resetCursorStyle())); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); addSmoothingAction(KisSmoothingOptions::NO_SMOOTHING, "set_no_brush_smoothing", i18nc("@action", "Brush Smoothing: Disabled"), KisIconUtils::loadIcon("smoothing-no"), collection); addSmoothingAction(KisSmoothingOptions::SIMPLE_SMOOTHING, "set_simple_brush_smoothing", i18nc("@action", "Brush Smoothing: Basic"), KisIconUtils::loadIcon("smoothing-basic"), collection); addSmoothingAction(KisSmoothingOptions::WEIGHTED_SMOOTHING, "set_weighted_brush_smoothing", i18nc("@action", "Brush Smoothing: Weighted"), KisIconUtils::loadIcon("smoothing-weighted"), collection); addSmoothingAction(KisSmoothingOptions::STABILIZER, "set_stabilizer_brush_smoothing", i18nc("@action", "Brush Smoothing: Stabilizer"), KisIconUtils::loadIcon("smoothing-stabilizer"), collection); } KisToolBrush::~KisToolBrush() { } void KisToolBrush::activate(ToolActivation activation, const QSet &shapes) { KisToolFreehand::activate(activation, shapes); connect(&m_signalMapper, SIGNAL(mapped(int)), SLOT(slotSetSmoothingType(int)), Qt::UniqueConnection); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolBrush::deactivate() { disconnect(&m_signalMapper, 0, this, 0); KisToolFreehand::deactivate(); } int KisToolBrush::smoothingType() const { return smoothingOptions()->smoothingType(); } bool KisToolBrush::smoothPressure() const { return smoothingOptions()->smoothPressure(); } int KisToolBrush::smoothnessQuality() const { return smoothingOptions()->smoothnessDistance(); } qreal KisToolBrush::smoothnessFactor() const { return smoothingOptions()->tailAggressiveness(); } void KisToolBrush::slotSetSmoothingType(int index) { /** * The slot can also be called from smoothing-type-switching * action that would mean the combo box will not be synchronized */ if (m_cmbSmoothingType->currentIndex() != index) { m_cmbSmoothingType->setCurrentIndex(index); } switch (index) { case 0: smoothingOptions()->setSmoothingType(KisSmoothingOptions::NO_SMOOTHING); showControl(m_sliderSmoothnessDistance, false); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, false); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 1: smoothingOptions()->setSmoothingType(KisSmoothingOptions::SIMPLE_SMOOTHING); showControl(m_sliderSmoothnessDistance, false); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, false); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 2: smoothingOptions()->setSmoothingType(KisSmoothingOptions::WEIGHTED_SMOOTHING); showControl(m_sliderSmoothnessDistance, true); showControl(m_sliderTailAggressiveness, true); showControl(m_chkSmoothPressure, true); showControl(m_chkUseScalableDistance, true); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 3: default: smoothingOptions()->setSmoothingType(KisSmoothingOptions::STABILIZER); showControl(m_sliderSmoothnessDistance, true); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, true); showControl(m_sliderDelayDistance, true); showControl(m_chkFinishStabilizedCurve, true); showControl(m_chkStabilizeSensors, true); } emit smoothingTypeChanged(); } void KisToolBrush::slotSetSmoothnessDistance(qreal distance) { smoothingOptions()->setSmoothnessDistance(distance); emit smoothnessQualityChanged(); } void KisToolBrush::slotSetTailAgressiveness(qreal argh_rhhrr) { smoothingOptions()->setTailAggressiveness(argh_rhhrr); emit smoothnessFactorChanged(); } // used with weighted smoothing void KisToolBrush::setSmoothPressure(bool value) { smoothingOptions()->setSmoothPressure(value); } void KisToolBrush::slotSetMagnetism(int magnetism) { m_magnetism = expf(magnetism / (double)MAXIMUM_MAGNETISM) / expf(1.0); } bool KisToolBrush::useScalableDistance() const { return smoothingOptions()->useScalableDistance(); } // used with weighted smoothing void KisToolBrush::setUseScalableDistance(bool value) { smoothingOptions()->setUseScalableDistance(value); emit useScalableDistanceChanged(); } void KisToolBrush::resetCursorStyle() { KisConfig cfg; CursorStyle cursorStyle = cfg.newCursorStyle(); // When the stabilizer is in use, we avoid using the brush outline cursor, // because it would hide the real position of the cursor to the user, // yielding unexpected results. if (smoothingOptions()->smoothingType() == KisSmoothingOptions::STABILIZER && smoothingOptions()->useDelayDistance() && cursorStyle == CURSOR_STYLE_NO_CURSOR) { useCursor(KisCursor::roundCursor()); } else { KisToolFreehand::resetCursorStyle(); } overrideCursorIfNotEditable(); } // stabilizer brush settings bool KisToolBrush::useDelayDistance() const { return smoothingOptions()->useDelayDistance(); } qreal KisToolBrush::delayDistance() const { return smoothingOptions()->delayDistance(); } void KisToolBrush::setUseDelayDistance(bool value) { smoothingOptions()->setUseDelayDistance(value); m_sliderDelayDistance->setEnabled(value); enableControl(m_chkFinishStabilizedCurve, !value); emit useDelayDistanceChanged(); } void KisToolBrush::setDelayDistance(qreal value) { smoothingOptions()->setDelayDistance(value); emit delayDistanceChanged(); } void KisToolBrush::setFinishStabilizedCurve(bool value) { smoothingOptions()->setFinishStabilizedCurve(value); emit finishStabilizedCurveChanged(); } bool KisToolBrush::finishStabilizedCurve() const { return smoothingOptions()->finishStabilizedCurve(); } void KisToolBrush::setStabilizeSensors(bool value) { smoothingOptions()->setStabilizeSensors(value); emit stabilizeSensorsChanged(); } bool KisToolBrush::stabilizeSensors() const { return smoothingOptions()->stabilizeSensors(); } void KisToolBrush::updateSettingsViews() { m_cmbSmoothingType->setCurrentIndex(smoothingOptions()->smoothingType()); m_sliderSmoothnessDistance->setValue(smoothingOptions()->smoothnessDistance()); m_chkDelayDistance->setChecked(smoothingOptions()->useDelayDistance()); m_sliderDelayDistance->setValue(smoothingOptions()->delayDistance()); m_sliderTailAggressiveness->setValue(smoothingOptions()->tailAggressiveness()); m_chkSmoothPressure->setChecked(smoothingOptions()->smoothPressure()); m_chkUseScalableDistance->setChecked(smoothingOptions()->useScalableDistance()); m_cmbSmoothingType->setCurrentIndex((int)smoothingOptions()->smoothingType()); m_chkStabilizeSensors->setChecked(smoothingOptions()->stabilizeSensors()); emit smoothnessQualityChanged(); emit smoothnessFactorChanged(); emit smoothPressureChanged(); emit smoothingTypeChanged(); emit useScalableDistanceChanged(); emit useDelayDistanceChanged(); emit delayDistanceChanged(); emit finishStabilizedCurveChanged(); emit stabilizeSensorsChanged(); KisTool::updateSettingsViews(); } QWidget * KisToolBrush::createOptionWidget() { QWidget *optionsWidget = KisToolFreehand::createOptionWidget(); optionsWidget->setObjectName(toolId() + "option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); optionsWidget->layout()->addWidget(specialSpacer); // Line smoothing configuration m_cmbSmoothingType = new QComboBox(optionsWidget); m_cmbSmoothingType->addItems(QStringList() << i18n("None") << i18n("Basic") << i18n("Weighted") << i18n("Stabilizer")); connect(m_cmbSmoothingType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetSmoothingType(int))); addOptionWidgetOption(m_cmbSmoothingType, new QLabel(i18n("Brush Smoothing:"))); m_sliderSmoothnessDistance = new KisDoubleSliderSpinBox(optionsWidget); m_sliderSmoothnessDistance->setRange(3.0, MAXIMUM_SMOOTHNESS_DISTANCE, 1); m_sliderSmoothnessDistance->setEnabled(true); connect(m_sliderSmoothnessDistance, SIGNAL(valueChanged(qreal)), SLOT(slotSetSmoothnessDistance(qreal))); m_sliderSmoothnessDistance->setValue(smoothingOptions()->smoothnessDistance()); addOptionWidgetOption(m_sliderSmoothnessDistance, new QLabel(i18n("Distance:"))); // Finish stabilizer curve m_chkFinishStabilizedCurve = new QCheckBox(optionsWidget); m_chkFinishStabilizedCurve->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkFinishStabilizedCurve->sizeHint().height())); connect(m_chkFinishStabilizedCurve, SIGNAL(toggled(bool)), this, SLOT(setFinishStabilizedCurve(bool))); m_chkFinishStabilizedCurve->setChecked(smoothingOptions()->finishStabilizedCurve()); // Delay Distance for Stabilizer QWidget* delayWidget = new QWidget(optionsWidget); QHBoxLayout* delayLayout = new QHBoxLayout(delayWidget); delayLayout->setContentsMargins(0,0,0,0); delayLayout->setSpacing(1); QLabel* delayLabel = new QLabel(i18n("Delay:"), optionsWidget); delayLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); delayLayout->addWidget(delayLabel); m_chkDelayDistance = new QCheckBox(optionsWidget); m_chkDelayDistance->setLayoutDirection(Qt::RightToLeft); delayWidget->setToolTip(i18n("Delay the brush stroke to make the line smoother")); connect(m_chkDelayDistance, SIGNAL(toggled(bool)), this, SLOT(setUseDelayDistance(bool))); delayLayout->addWidget(m_chkDelayDistance); m_sliderDelayDistance = new KisDoubleSliderSpinBox(optionsWidget); m_sliderDelayDistance->setToolTip(i18n("Radius where the brush is blocked")); m_sliderDelayDistance->setRange(0, 500); m_sliderDelayDistance->setSuffix(i18n(" px")); connect(m_sliderDelayDistance, SIGNAL(valueChanged(qreal)), SLOT(setDelayDistance(qreal))); addOptionWidgetOption(m_sliderDelayDistance, delayWidget); addOptionWidgetOption(m_chkFinishStabilizedCurve, new QLabel(i18n("Finish line:"))); m_sliderDelayDistance->setValue(smoothingOptions()->delayDistance()); m_chkDelayDistance->setChecked(smoothingOptions()->useDelayDistance()); // if the state is not flipped, then the previous line doesn't generate any signals setUseDelayDistance(m_chkDelayDistance->isChecked()); // Stabilize sensors m_chkStabilizeSensors = new QCheckBox(optionsWidget); m_chkStabilizeSensors->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkStabilizeSensors->sizeHint().height())); connect(m_chkStabilizeSensors, SIGNAL(toggled(bool)), this, SLOT(setStabilizeSensors(bool))); m_chkStabilizeSensors->setChecked(smoothingOptions()->stabilizeSensors()); addOptionWidgetOption(m_chkStabilizeSensors, new QLabel(i18n("Stabilize Sensors:"))); m_sliderTailAggressiveness = new KisDoubleSliderSpinBox(optionsWidget); m_sliderTailAggressiveness->setRange(0.0, 1.0, 2); m_sliderTailAggressiveness->setEnabled(true); connect(m_sliderTailAggressiveness, SIGNAL(valueChanged(qreal)), SLOT(slotSetTailAgressiveness(qreal))); m_sliderTailAggressiveness->setValue(smoothingOptions()->tailAggressiveness()); addOptionWidgetOption(m_sliderTailAggressiveness, new QLabel(i18n("Stroke Ending:"))); m_chkSmoothPressure = new QCheckBox(optionsWidget); m_chkSmoothPressure->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkSmoothPressure->sizeHint().height())); m_chkSmoothPressure->setChecked(smoothingOptions()->smoothPressure()); connect(m_chkSmoothPressure, SIGNAL(toggled(bool)), this, SLOT(setSmoothPressure(bool))); addOptionWidgetOption(m_chkSmoothPressure, new QLabel(QString("%1:").arg(i18n("Smooth Pressure")))); m_chkUseScalableDistance = new QCheckBox(optionsWidget); m_chkUseScalableDistance->setChecked(smoothingOptions()->useScalableDistance()); m_chkUseScalableDistance->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkUseScalableDistance->sizeHint().height())); m_chkUseScalableDistance->setToolTip(i18nc("@info:tooltip", "Scalable distance takes zoom level " "into account and makes the distance " "be visually constant whatever zoom " "level is chosen")); connect(m_chkUseScalableDistance, SIGNAL(toggled(bool)), this, SLOT(setUseScalableDistance(bool))); addOptionWidgetOption(m_chkUseScalableDistance, new QLabel(QString("%1:").arg(i18n("Scalable Distance")))); // add a line spacer so we know that the next set of options are for different settings QFrame* line = new QFrame(optionsWidget); line->setObjectName(QString::fromUtf8("line")); line->setFrameShape(QFrame::HLine); addOptionWidgetOption(line); // Drawing assistant configuration QWidget* assistantWidget = new QWidget(optionsWidget); QGridLayout* assistantLayout = new QGridLayout(assistantWidget); assistantLayout->setContentsMargins(10,0,0,0); assistantLayout->setSpacing(5); m_chkAssistant = new QCheckBox(optionsWidget); m_chkAssistant->setText(i18n("Snap to Assistants")); assistantWidget->setToolTip(i18n("You need to add Ruler Assistants before this tool will work.")); connect(m_chkAssistant, SIGNAL(toggled(bool)), this, SLOT(setAssistant(bool))); - assistantLayout->addWidget(m_chkAssistant, 1, 1, 1, 1, Qt::AlignLeft); + addOptionWidgetOption(assistantWidget, m_chkAssistant); m_sliderMagnetism = new KisSliderSpinBox(optionsWidget); m_sliderMagnetism->setToolTip(i18n("Assistant Magnetism")); m_sliderMagnetism->setRange(0, MAXIMUM_MAGNETISM); m_sliderMagnetism->setValue(m_magnetism * MAXIMUM_MAGNETISM); connect(m_sliderMagnetism, SIGNAL(valueChanged(int)), SLOT(slotSetMagnetism(int))); QAction *toggleaction = new QAction(i18n("Toggle Assistant"), this); addAction("toggle_assistant", toggleaction); toggleaction->setShortcut(QKeySequence(Qt::ControlModifier + Qt::ShiftModifier + Qt::Key_L)); connect(toggleaction, SIGNAL(triggered(bool)), m_chkAssistant, SLOT(toggle())); - - addOptionWidgetOption(m_sliderMagnetism, assistantWidget); + QLabel* magnetismLabel = new QLabel(i18n("Magnetism:")); + addOptionWidgetOption(m_sliderMagnetism, magnetismLabel); QLabel* snapSingleLabel = new QLabel(i18n("Snap Single:")); m_chkOnlyOneAssistant = new QCheckBox(optionsWidget); m_chkOnlyOneAssistant->setToolTip(i18nc("@info:tooltip","Make it only snap to a single assistant, prevents snapping mess while using the infinite assistants.")); m_chkOnlyOneAssistant->setCheckState(Qt::Checked);//turn on by default. connect(m_chkOnlyOneAssistant, SIGNAL(toggled(bool)), this, SLOT(setOnlyOneAssistantSnap(bool))); addOptionWidgetOption(m_chkOnlyOneAssistant, snapSingleLabel); // set the assistant snapping options to hidden by default and toggle their visibility based based off snapping checkbox m_sliderMagnetism->setVisible(false); m_chkOnlyOneAssistant->setVisible(false); snapSingleLabel->setVisible(false); + magnetismLabel->setVisible(false); connect(m_chkAssistant, SIGNAL(toggled(bool)), m_sliderMagnetism, SLOT(setVisible(bool))); connect(m_chkAssistant, SIGNAL(toggled(bool)), m_chkOnlyOneAssistant, SLOT(setVisible(bool))); connect(m_chkAssistant, SIGNAL(toggled(bool)), snapSingleLabel, SLOT(setVisible(bool))); - + connect(m_chkAssistant, SIGNAL(toggled(bool)), magnetismLabel, SLOT(setVisible(bool))); KisConfig cfg; slotSetSmoothingType(cfg.lineSmoothingType()); return optionsWidget; } diff --git a/plugins/tools/basictools/kis_tool_line.cc b/plugins/tools/basictools/kis_tool_line.cc index c0231b43f8..6af2a46605 100644 --- a/plugins/tools/basictools/kis_tool_line.cc +++ b/plugins/tools/basictools/kis_tool_line.cc @@ -1,376 +1,371 @@ /* * kis_tool_line.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2003 Boudewijn Rempt * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_line.h" #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_canvas2.h" #include #include #include #include "kis_painting_information_builder.h" #include "kis_tool_line_helper.h" #define ENABLE_RECORDING const KisCoordinatesConverter* getCoordinatesConverter(KoCanvasBase * canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); return kritaCanvas->coordinatesConverter(); } KisToolLine::KisToolLine(KoCanvasBase * canvas) : KisToolShape(canvas, KisCursor::load("tool_line_cursor.png", 6, 6)), m_showGuideline(true), m_strokeIsRunning(false), m_infoBuilder(new KisConverterPaintingInformationBuilder(getCoordinatesConverter(canvas))), m_helper(new KisToolLineHelper(m_infoBuilder.data(), kundo2_i18n("Draw Line"))), m_strokeUpdateCompressor(500, KisSignalCompressor::POSTPONE), m_longStrokeUpdateCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) { setObjectName("tool_line"); setSupportOutline(true); connect(&m_strokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); connect(&m_longStrokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); } KisToolLine::~KisToolLine() { } -int KisToolLine::flags() const -{ - return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET; -} - void KisToolLine::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolLine::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolLine::deactivate() { KisToolPaint::deactivate(); cancelStroke(); } QWidget* KisToolLine::createOptionWidget() { QWidget* widget = KisToolPaint::createOptionWidget(); m_chkUseSensors = new QCheckBox(i18n("Use sensors")); addOptionWidgetOption(m_chkUseSensors); m_chkShowPreview = new QCheckBox(i18n("Show Preview")); addOptionWidgetOption(m_chkShowPreview); m_chkShowGuideline = new QCheckBox(i18n("Show Guideline")); addOptionWidgetOption(m_chkShowGuideline); // hook up connections for value changing connect(m_chkUseSensors, SIGNAL(clicked(bool)), this, SLOT(setUseSensors(bool)) ); connect(m_chkShowPreview, SIGNAL(clicked(bool)), this, SLOT(setShowPreview(bool)) ); connect(m_chkShowGuideline, SIGNAL(clicked(bool)), this, SLOT(setShowGuideline(bool)) ); // read values in from configuration m_chkUseSensors->setChecked(configGroup.readEntry("useSensors", true)); m_chkShowPreview->setChecked(configGroup.readEntry("showPreview", true)); m_chkShowGuideline->setChecked(configGroup.readEntry("showGuideline", true)); return widget; } void KisToolLine::setUseSensors(bool value) { configGroup.writeEntry("useSensors", value); } void KisToolLine::setShowGuideline(bool value) { m_showGuideline = value; configGroup.writeEntry("showGuideline", value); } void KisToolLine::setShowPreview(bool value) { configGroup.writeEntry("showPreview", value); } void KisToolLine::requestStrokeCancellation() { cancelStroke(); } void KisToolLine::requestStrokeEnd() { // Terminate any in-progress strokes if (nodePaintAbility() == PAINT && m_helper->isRunning()) { endStroke(); } } void KisToolLine::updatePreviewTimer(bool showGuideline) { // If the user disables the guideline, we will want to try to draw some // preview lines even if they're slow, so set the timer to FIRST_ACTIVE. if (showGuideline) { m_strokeUpdateCompressor.setMode(KisSignalCompressor::POSTPONE); } else { m_strokeUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } } void KisToolLine::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if(mode() == KisTool::PAINT_MODE) { paintLine(gc,QRect()); } KisToolPaint::paint(gc,converter); } void KisToolLine::beginPrimaryAction(KoPointerEvent *event) { NodePaintAbility nodeAbility = nodePaintAbility(); if (nodeAbility == NONE || !nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); // Always show guideline on vector layers m_showGuideline = m_chkShowGuideline->isChecked() || nodeAbility != PAINT; updatePreviewTimer(m_showGuideline); m_helper->setEnabled(nodeAbility == PAINT); m_helper->setUseSensors(m_chkUseSensors->isChecked()); m_helper->start(event, canvas()->resourceManager()); m_startPoint = convertToPixelCoordAndSnap(event); m_endPoint = m_startPoint; m_lastUpdatedPoint = m_startPoint; m_strokeIsRunning = true; } void KisToolLine::updateStroke() { if (!m_strokeIsRunning) return; m_helper->repaintLine(canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolLine::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeIsRunning) return; // First ensure the old guideline is deleted updateGuideline(); QPointF pos = convertToPixelCoordAndSnap(event); if (event->modifiers() == Qt::AltModifier) { QPointF trans = pos - m_endPoint; m_helper->translatePoints(trans); m_startPoint += trans; m_endPoint += trans; } else if (event->modifiers() == Qt::ShiftModifier) { pos = straightLine(pos); m_helper->addPoint(event, pos); } else { m_helper->addPoint(event, pos); } m_endPoint = pos; // Draw preview if requested if (m_chkShowPreview->isChecked()) { // If the cursor has moved a significant amount, immediately clear the // current preview and redraw. Otherwise, do slow redraws periodically. auto updateDistance = (pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength(); if (updateDistance > 10) { m_helper->clearPaint(); m_longStrokeUpdateCompressor.stop(); m_strokeUpdateCompressor.start(); m_lastUpdatedPoint = pos; } else if (updateDistance > 1) { m_longStrokeUpdateCompressor.start(); } } updateGuideline(); KisToolPaint::requestUpdateOutline(event->point, event); } void KisToolLine::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); updateGuideline(); endStroke(); } void KisToolLine::endStroke() { NodePaintAbility nodeAbility = nodePaintAbility(); if (!m_strokeIsRunning || m_startPoint == m_endPoint || nodeAbility == NONE) { return; } if (nodeAbility == PAINT) { updateStroke(); m_helper->end(); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_startPoint)); path->lineTo(resolutionMatrix.map(m_endPoint)); path->normalize(); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); path->setStroke(border); KUndo2Command * cmd = canvas()->shapeController()->addShape(path); canvas()->addCommand(cmd); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } void KisToolLine::cancelStroke() { if (!m_strokeIsRunning) return; if (m_startPoint == m_endPoint) return; /** * The actual stroke is run by the timer so it is a legal * situation when m_strokeIsRunning is true, but the actual redraw * stroke is not running. */ if (m_helper->isRunning()) { m_helper->cancel(); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } QPointF KisToolLine::straightLine(QPointF point) { const QPointF lineVector = point - m_startPoint; qreal lineAngle = std::atan2(lineVector.y(), lineVector.x()); if (lineAngle < 0) { lineAngle += 2 * M_PI; } const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24; const quint32 constrainedLineIndex = static_cast((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5); const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES; const qreal lineLength = std::sqrt((lineVector.x() * lineVector.x()) + (lineVector.y() * lineVector.y())); const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle)); const QPointF result = m_startPoint + constrainedLineVector; return result; } void KisToolLine::updateGuideline() { if (canvas()) { QRectF bound(m_startPoint, m_endPoint); canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3))); } } void KisToolLine::paintLine(QPainter& gc, const QRect&) { QPointF viewStartPos = pixelToView(m_startPoint); QPointF viewStartEnd = pixelToView(m_endPoint); if (m_showGuideline && canvas()) { QPainterPath path; path.moveTo(viewStartPos); path.lineTo(viewStartEnd); paintToolOutline(&gc, path); } } QString KisToolLine::quickHelp() const { return i18n("Alt+Drag will move the origin of the currently displayed line around, Shift+Drag will force you to draw straight lines"); } diff --git a/plugins/tools/basictools/kis_tool_line.h b/plugins/tools/basictools/kis_tool_line.h index e9db2097f2..281b4b0356 100644 --- a/plugins/tools/basictools/kis_tool_line.h +++ b/plugins/tools/basictools/kis_tool_line.h @@ -1,137 +1,136 @@ /* * kis_tool_line.h - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_LINE_H_ #define KIS_TOOL_LINE_H_ #include "kis_tool_shape.h" #include #include #include #include "kis_global.h" #include "kis_types.h" #include "KoToolFactoryBase.h" #include "flake/kis_node_shape.h" #include "kis_signal_compressor.h" #include #include class QPoint; class KoCanvasBase; class QCheckBox; class KisPaintingInformationBuilder; class KisToolLineHelper; class KisToolLine : public KisToolShape { Q_OBJECT public: KisToolLine(KoCanvasBase * canvas); ~KisToolLine() override; void requestStrokeCancellation() override; void requestStrokeEnd() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; - int flags() const override; void paint(QPainter& gc, const KoViewConverter &converter) override; QString quickHelp() const override; protected Q_SLOTS: void resetCursorStyle() override; private Q_SLOTS: void updateStroke(); void setUseSensors(bool value); void setShowPreview(bool value); void setShowGuideline(bool value); private: void paintLine(QPainter& gc, const QRect& rc); QPointF straightLine(QPointF point); void updateGuideline(); void updatePreviewTimer(bool showGuide); QWidget* createOptionWidget() override; void endStroke(); void cancelStroke(); private: bool m_showGuideline; QPointF m_startPoint; QPointF m_endPoint; QPointF m_lastUpdatedPoint; bool m_strokeIsRunning; QCheckBox *m_chkUseSensors; QCheckBox *m_chkShowPreview; QCheckBox *m_chkShowGuideline; QScopedPointer m_infoBuilder; QScopedPointer m_helper; KisSignalCompressor m_strokeUpdateCompressor; KisSignalCompressor m_longStrokeUpdateCompressor; KConfigGroup configGroup; }; class KisToolLineFactory : public KoToolFactoryBase { public: KisToolLineFactory() : KoToolFactoryBase("KritaShape/KisToolLine") { setToolTip(i18n("Line Tool")); // Temporarily setSection(TOOL_TYPE_SHAPE); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setPriority(1); setIconName(koIconNameCStr("krita_tool_line")); } ~KisToolLineFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolLine(canvas); } }; #endif //KIS_TOOL_LINE_H_ diff --git a/plugins/tools/basictools/kis_tool_multihand.cpp b/plugins/tools/basictools/kis_tool_multihand.cpp index 1b4cdeb39b..2c79761413 100644 --- a/plugins/tools/basictools/kis_tool_multihand.cpp +++ b/plugins/tools/basictools/kis_tool_multihand.cpp @@ -1,411 +1,420 @@ /* * Copyright (c) 2011 Lukáš Tvrdý * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_multihand.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_tool_multihand_helper.h" static const int MAXIMUM_BRUSHES = 50; #include #ifdef Q_OS_WIN // quoting DRAND48(3) man-page: // These functions are declared obsolete by SVID 3, // which states that rand(3) should be used instead. #define drand48() (static_cast(qrand()) / static_cast(RAND_MAX)) #endif KisToolMultihand::KisToolMultihand(KoCanvasBase *canvas) : KisToolBrush(canvas), m_transformMode(SYMMETRY), m_angle(0), m_handsCount(6), m_mirrorVertically(false), m_mirrorHorizontally(false), m_showAxes(false), m_translateRadius(100), m_setupAxesFlag(false) , customUI(0) { m_helper = new KisToolMultihandHelper(paintingInformationBuilder(), kundo2_i18n("Multibrush Stroke"), recordingAdapter()); resetHelper(m_helper); if (image()) { m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); } } KisToolMultihand::~KisToolMultihand() { } void KisToolMultihand::beginPrimaryAction(KoPointerEvent *event) { if(m_setupAxesFlag) { setMode(KisTool::OTHER); m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else { initTransformations(); KisToolFreehand::beginPrimaryAction(event); } } void KisToolMultihand::continuePrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else { KisToolFreehand::continuePrimaryAction(event); } } void KisToolMultihand::endPrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { setMode(KisTool::HOVER_MODE); requestUpdateOutline(event->point, 0); finishAxesSetup(); } else { KisToolFreehand::endPrimaryAction(event); } } void KisToolMultihand::paint(QPainter& gc, const KoViewConverter &converter) { if(m_setupAxesFlag) { int diagonal = (currentImage()->height() + currentImage()->width()); QPainterPath path; path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle), m_axesPoint.y()-diagonal*sin(m_angle)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle), m_axesPoint.y()+diagonal*sin(m_angle)); path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()-diagonal*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()+diagonal*sin(m_angle+M_PI_2)); paintToolOutline(&gc, pixelToView(path)); } else { KisToolFreehand::paint(gc, converter); if(m_showAxes){ int diagonal = (currentImage()->height() + currentImage()->width()); QPainterPath path; path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle), m_axesPoint.y()-diagonal*sin(m_angle)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle), m_axesPoint.y()+diagonal*sin(m_angle)); path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()-diagonal*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()+diagonal*sin(m_angle+M_PI_2)); paintToolOutline(&gc, pixelToView(path)); } } } void KisToolMultihand::initTransformations() { QVector transformations; QTransform m; if(m_transformMode == SYMMETRY) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount; for(int i = 0; i < m_handsCount; i++) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep; } } else if(m_transformMode == MIRROR) { transformations << m; if (m_mirrorHorizontally) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically && m_mirrorHorizontally){ m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } else if(m_transformMode == SNOWFLAKE) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount/4; for(int i = 0; i < m_handsCount*4; i++) { if ((i%2)==1) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.scale(-1,1); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } else { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } } } else /* if(m_transformationNode == TRANSLATE) */ { /** * TODO: currently, the seed is the same for all the * strokes */ for (int i = 0; i < m_handsCount; i++){ qreal angle = drand48() * M_PI * 2; qreal length = drand48(); // convert the Polar coordinates to Cartesian coordinates qreal nx = (m_translateRadius * cos(angle) * length); qreal ny = (m_translateRadius * sin(angle) * length); m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.translate(nx,ny); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } m_helper->setupTransformations(transformations); } QWidget* KisToolMultihand::createOptionWidget() { QWidget *widget = KisToolBrush::createOptionWidget(); customUI = new KisToolMultiHandConfigWidget(); // brush smoothing option. customUI->layout()->addWidget(widget); customUI->smoothingOptionsLayout->addWidget(widget); // setup common parameters that all of the modes will see connect(customUI->showAxesCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetAxesVisible(bool))); customUI->showAxesCheckbox->setChecked((bool)m_configGroup.readEntry("showAxes", false)); customUI->moveOriginButton->setCheckable(true); connect(customUI->moveOriginButton, SIGNAL(clicked(bool)),this, SLOT(activateAxesPointModeSetup())); + connect(customUI->resetOriginButton, SIGNAL(released()), this, SLOT(resetAxes())); + customUI->multihandTypeCombobox->addItem(i18n("Symmetry"),int(SYMMETRY)); // axis mode customUI->multihandTypeCombobox->addItem(i18n("Mirror"),int(MIRROR)); customUI->multihandTypeCombobox->addItem(i18n("Translate"),int(TRANSLATE)); customUI->multihandTypeCombobox->addItem(i18n("Snowflake"),int(SNOWFLAKE)); connect(customUI->multihandTypeCombobox,SIGNAL(currentIndexChanged(int)),this, SLOT(slotSetTransformMode(int))); customUI->multihandTypeCombobox->setCurrentIndex(m_configGroup.readEntry("transformMode", 0)); slotSetTransformMode(customUI->multihandTypeCombobox->currentIndex()); customUI->axisRotationSpinbox->setSuffix(QChar(Qt::Key_degree)); // origin rotation customUI->axisRotationSpinbox->setRange(0.0, 90.0); customUI->axisRotationSpinbox->setValue(m_configGroup.readEntry("axesAngle", 0.0)); connect( customUI->axisRotationSpinbox, SIGNAL(valueChanged(int)),this, SLOT(slotSetAxesAngle(int))); // symmetry mode options customUI->brushCountSpinBox->setRange(1, MAXIMUM_BRUSHES); connect(customUI->brushCountSpinBox, SIGNAL(valueChanged(int)),this, SLOT(slotSetHandsCount(int))); customUI->brushCountSpinBox->setValue(m_configGroup.readEntry("handsCount", 4)); // mirror mode specific options connect(customUI->horizontalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorHorizontally(bool))); customUI->horizontalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorHorizontally", false)); connect(customUI->verticalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorVertically(bool))); customUI->verticalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorVertically", false)); // translate mode options customUI->translationRadiusSpinbox->setRange(0, 200); customUI->translationRadiusSpinbox->setSuffix(i18n(" px")); customUI->translationRadiusSpinbox->setValue(m_configGroup.readEntry("translateRadius", 0)); connect(customUI->translationRadiusSpinbox,SIGNAL(valueChanged(int)),this,SLOT(slotSetTranslateRadius(int))); // snowflake re-uses the existing options, so there is no special parameters for that... return static_cast(customUI); // keeping it in the native class until the end allows us to access the UI components } void KisToolMultihand::activateAxesPointModeSetup() { if (customUI->moveOriginButton->isChecked()){ m_setupAxesFlag = true; useCursor(KisCursor::crossCursor()); updateCanvas(); } else { finishAxesSetup(); } } +void KisToolMultihand::resetAxes() +{ + m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); + finishAxesSetup(); +} + + void KisToolMultihand::finishAxesSetup() { m_setupAxesFlag = false; customUI->moveOriginButton->setChecked(false); resetCursorStyle(); updateCanvas(); } void KisToolMultihand::updateCanvas() { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); Q_ASSERT(kisCanvas); kisCanvas->updateCanvas(); } void KisToolMultihand::slotSetHandsCount(int count) { m_handsCount = count; m_configGroup.writeEntry("handsCount", count); } void KisToolMultihand::slotSetAxesAngle(int angle) { //negative so axes rotates counter clockwise m_angle = -angle*M_PI/180; updateCanvas(); m_configGroup.writeEntry("axesAngle", angle); } void KisToolMultihand::slotSetTransformMode(int index) { m_transformMode = enumTransforModes(customUI->multihandTypeCombobox->itemData(index).toInt()); m_configGroup.writeEntry("transformMode", index); // hide all of the UI elements by default customUI->horizontalCheckbox->setVisible(false); customUI->verticalCheckbox->setVisible(false); customUI->translationRadiusSpinbox->setVisible(false); customUI->radiusLabel->setVisible(false); customUI->brushCountSpinBox->setVisible(false); customUI->brushesLabel->setVisible(false); // turn on what we need if (index == int(MIRROR)) { customUI->horizontalCheckbox->setVisible(true); customUI->verticalCheckbox->setVisible(true); } if (index == int(TRANSLATE)) { customUI->translationRadiusSpinbox->setVisible(true); customUI->radiusLabel->setVisible(true); } if (index == int(SYMMETRY) || index == int(SNOWFLAKE) || index == int(TRANSLATE) ) { customUI->brushCountSpinBox->setVisible(true); customUI->brushesLabel->setVisible(true); } } void KisToolMultihand::slotSetAxesVisible(bool vis) { m_showAxes = vis; updateCanvas(); m_configGroup.writeEntry("showAxes", vis); } void KisToolMultihand::slotSetMirrorVertically(bool mirror) { m_mirrorVertically = mirror; m_configGroup.writeEntry("mirrorVertically", mirror); } void KisToolMultihand::slotSetMirrorHorizontally(bool mirror) { m_mirrorHorizontally = mirror; m_configGroup.writeEntry("mirrorHorizontally", mirror); } void KisToolMultihand::slotSetTranslateRadius(int radius) { m_translateRadius = radius; m_configGroup.writeEntry("translateRadius", radius); } diff --git a/plugins/tools/basictools/kis_tool_multihand.h b/plugins/tools/basictools/kis_tool_multihand.h index ed096bc726..93b0e68c62 100644 --- a/plugins/tools/basictools/kis_tool_multihand.h +++ b/plugins/tools/basictools/kis_tool_multihand.h @@ -1,120 +1,121 @@ /* * Copyright (c) 2011 Lukáš Tvrdý * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_MULTIHAND_H #define __KIS_TOOL_MULTIHAND_H #include "kis_tool_brush.h" #include #include "kis_tool_multihand_config.h" class QPushButton; class QCheckBox; class QComboBox; class QStackedWidget; class KisSliderSpinBox; class KisToolMultihandHelper; class KisToolMultihand : public KisToolBrush { Q_OBJECT public: KisToolMultihand(KoCanvasBase *canvas); ~KisToolMultihand() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; protected: void paint(QPainter& gc, const KoViewConverter &converter) override; QWidget* createOptionWidget() override; private: void initTransformations(); void finishAxesSetup(); void updateCanvas(); private Q_SLOTS: void activateAxesPointModeSetup(); + void resetAxes(); void slotSetHandsCount(int count); void slotSetAxesAngle(int angle); void slotSetTransformMode(int qcomboboxIndex); void slotSetAxesVisible(bool vis); void slotSetMirrorVertically(bool mirror); void slotSetMirrorHorizontally(bool mirror); void slotSetTranslateRadius(int radius); private: KisToolMultihandHelper *m_helper; enum enumTransforModes { SYMMETRY, MIRROR, TRANSLATE, SNOWFLAKE }; enumTransforModes m_transformMode; QPointF m_axesPoint; qreal m_angle; int m_handsCount; bool m_mirrorVertically; bool m_mirrorHorizontally; bool m_showAxes; int m_translateRadius; bool m_setupAxesFlag; QComboBox * m_transformModesComboBox; KisSliderSpinBox *m_handsCountSlider; KisDoubleSliderSpinBox *m_axesAngleSlider; QCheckBox *m_axesChCkBox; QStackedWidget *m_modeCustomOption; QCheckBox *m_mirrorVerticallyChCkBox; QCheckBox *m_mirrorHorizontallyChCkBox; KisSliderSpinBox *m_translateRadiusSlider; QPushButton *m_axesPointBtn; KisToolMultiHandConfigWidget* customUI; }; class KisToolMultiBrushFactory : public KoToolFactoryBase { public: KisToolMultiBrushFactory() : KoToolFactoryBase("KritaShape/KisToolMultiBrush") { setToolTip(i18n("Multibrush Tool")); // Temporarily setSection(TOOL_TYPE_SHAPE); setIconName(koIconNameCStr("krita_tool_multihand")); setShortcut(QKeySequence(Qt::Key_Q)); setPriority(11); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolMultiBrushFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolMultihand(canvas); } }; #endif /* __KIS_TOOL_MULTIHAND_H */ diff --git a/plugins/tools/basictools/wdgmovetool.ui b/plugins/tools/basictools/wdgmovetool.ui index d3e6cb9995..4191e70a6c 100644 --- a/plugins/tools/basictools/wdgmovetool.ui +++ b/plugins/tools/basictools/wdgmovetool.ui @@ -1,261 +1,342 @@ WdgMoveTool 0 0 - 271 - 394 + 245 + 517 - - + + + + + 221 + 101 + + Selection Mode Move the layer that you have currently selected in the layerbox with its masks. Shortcut: ctrl-click. &Move current layer true Move the first layer with visible content at the place where you click. This will also select that layer in the layerbox. Mo&ve layer with content false Move the group containing the first layer that contains visible content. Shortcut: ctrl-shift-click. Move &the whole group - - - - + + + + 227 + 91 + + - Shortcut Move Distance + Move Shortcut - - + + + + + 0 + 0 + + - Number of pixels to move after move shortcut keypress. + When holding shift, move keyboard shortcuts scale up by this amount. 1.000000000000000 - 100000.000000000000000 + 1000.000000000000000 - 1.000000000000000 + 10.000000000000000 - + + + + + 0 + 0 + + + + Multiplier: + + + + - - + + + + + 0 + 0 + + - When holding shift, move keyboard shortcuts scale up by this amount. + Number of pixels to move after move shortcut keypress. + + + px 1.000000000000000 - 1000.000000000000000 + 100000.000000000000000 - 10.000000000000000 + 1.000000000000000 - - + + + + + 0 + 0 + + + + Amount: + + + + + + + + 0 + 0 + + - Large Move Scale + Unit: - - - - Show coordinates on canvas - - - Show coordinates - - - - + - + 0 0 - 240 - 70 + 100 + 101 Position false false false - 2 + 5 5 - 0 + 5 0 QFormLayout::AllNonFixedFieldsGrow + + 5 + + + 5 + + + 5 + + + 5 + 0 0 Horizontal Translation Qt::LeftToRight &x: translateXBox - + 0 0 Horizontal Translation Qt::LeftToRight - + 0 0 Vertical Translation 0 0 Vertical Translation &y: translateYBox - - + + + + Show coordinates on canvas + + + Show coordinates on canvas + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + KisDoubleParseSpinBox QDoubleSpinBox
kis_double_parse_spin_box.h
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
diff --git a/plugins/tools/basictools/wdgmultihandtool.ui b/plugins/tools/basictools/wdgmultihandtool.ui index f8d2a974f4..b4aed1bbd0 100644 --- a/plugins/tools/basictools/wdgmultihandtool.ui +++ b/plugins/tools/basictools/wdgmultihandtool.ui @@ -1,176 +1,258 @@ WdgMultiHandTool 0 0 - 212 - 293 + 200 + 282 + + + 200 + 0 + + 0 10 - + + + QFrame::Plain + + + Qt::Horizontal + + + + + 4 7 - + + + + 0 + 0 + + Type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + + + true + + + + 0 + 0 + + + + Move + + + false + + + + + + + true + + + + 0 + 0 + + + + Reset + + + false + + + + + + + + + + + + + 0 + 0 + + - Brushes: + Rotation: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Radius: + + + + + 0 + 0 + - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + 15 + - - + + - - + + - + 0 0 - Rotation: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Show Origin - - - - Vertical + + + + + 0 + 0 + - - - - - Horizontal + Brushes: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - true - + + - + 0 0 - Move Origin - - - false + Horizontal - - + + - + 0 0 - - - 0 - 15 - + + Qt::LeftToRight + + + Origin: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + Vertical + + - - + + - + 0 0 - Show Origin + Radius: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::Vertical 20 40 KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.ui b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.ui index e359fb7f5e..43603bf5c0 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.ui +++ b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.ui @@ -1,253 +1,283 @@ DefaultToolGeometryWidget 0 0 - 305 + 182 303 Qt::NoFocus 1 0 0 X: 0 0 + + + 30 + 0 + + -10000.000000000000000 10000.000000000000000 0 0 + + + 30 + 0 + + -10000.000000000000000 10000.000000000000000 0 0 Y: 0 0 + + + 30 + 0 + + -10000.000000000000000 10000.000000000000000 0 0 + + + 30 + 0 + + -10000.000000000000000 10000.000000000000000 Qt::Horizontal 1 0 0 Qt::Horizontal QSizePolicy::MinimumExpanding 20 20 1 Anchor Lock <html><head/><body><p>When &quot;Uniform Scaling&quot; is <span style=" font-weight:600;">enabled</span>, the shape's stroke is scaled with the shape itself. </p><p>In <span style=" font-weight:600;">disabled</span> state, the shape is only resized, keeping the stroke width and style intact.</p></body></html> Uniform Scaling + + + 50 + 0 + + <html><head/><body><p>In &quot;Global Coordinates&quot; mode Width and Height fields show the size of the shape's bounding box in image-aligned coordinates, even when the shape is rotated or has any other transform. </p><p>If &quot;Global Coordinates&quot; mode is disabled, Width and Height fields show the shape's &quot;local&quot; size, before application of any transformations.</p></body></html> Global Coordinates Qt::Horizontal Qt::Vertical 20 1 KoAspectButton QWidget
KoAspectButton.h
1
KisDoubleParseUnitSpinBox QDoubleSpinBox
kis_double_parse_unit_spin_box.h
KoAnchorSelectionWidget QWidget
KoAnchorSelectionWidget.h
1
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
positionXSpinBox positionYSpinBox widthSpinBox heightSpinBox
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.cpp index 3851b5b840..8127c711a0 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.cpp @@ -1,90 +1,91 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DefaultToolTabbedWidget.h" #include #include "kis_icon_utils.h" #include "DefaultToolGeometryWidget.h" #include "KoStrokeConfigWidget.h" #include "KoFillConfigWidget.h" #include #include DefaultToolTabbedWidget::DefaultToolTabbedWidget(KoInteractionTool *tool, QWidget *parent) : KoTitledTabWidget(parent) -{ +{ setObjectName("default-tool-tabbed-widget"); DefaultToolGeometryWidget *geometryWidget = new DefaultToolGeometryWidget(tool, this); geometryWidget->setWindowTitle(i18n("Geometry")); addTab(geometryWidget, KisIconUtils::loadIcon("geometry"), QString()); m_strokeWidget = new KoStrokeConfigWidget(tool->canvas(), this); m_strokeWidget->setWindowTitle(i18n("Stroke")); KisDocumentAwareSpinBoxUnitManager* managerLineWidth = new KisDocumentAwareSpinBoxUnitManager(m_strokeWidget); KisDocumentAwareSpinBoxUnitManager* managerMitterLimit = new KisDocumentAwareSpinBoxUnitManager(m_strokeWidget); managerLineWidth->setApparentUnitFromSymbol("px"); managerMitterLimit->setApparentUnitFromSymbol("px"); //set unit to px by default m_strokeWidget->setUnitManagers(managerLineWidth, managerMitterLimit); addTab(m_strokeWidget, KisIconUtils::loadIcon("krita_tool_line"), QString()); + m_fillWidget = new KoFillConfigWidget(tool->canvas(), KoFlake::Fill, this); m_fillWidget->setWindowTitle(i18n("Fill")); addTab(m_fillWidget, KisIconUtils::loadIcon("krita_tool_color_fill"), QString()); connect(this, SIGNAL(currentChanged(int)), SLOT(slotCurrentIndexChanged(int))); m_oldTabIndex = currentIndex(); } DefaultToolTabbedWidget::~DefaultToolTabbedWidget() { } void DefaultToolTabbedWidget::activate() { m_fillWidget->activate(); m_strokeWidget->activate(); } void DefaultToolTabbedWidget::deactivate() { m_fillWidget->deactivate(); m_strokeWidget->deactivate(); } void DefaultToolTabbedWidget::slotCurrentIndexChanged(int current) { if (m_oldTabIndex == FillTab) { emit sigSwitchModeEditFillGradient(false); } else if (m_oldTabIndex == StrokeTab) { emit sigSwitchModeEditStrokeGradient(false); } m_oldTabIndex = current; if (current == FillTab) { emit sigSwitchModeEditFillGradient(true); } else if (m_oldTabIndex == StrokeTab) { emit sigSwitchModeEditStrokeGradient(true); } } diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc index de663a9234..c2794169b0 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc @@ -1,253 +1,245 @@ /* * kis_tool_select_contiguous - part of Krayon^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2012 José Luis Vergara * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_select_contiguous.h" #include #include #include #include #include #include -#include #include #include #include #include "KoPointerEvent.h" #include "KoViewConverter.h" #include "kis_cursor.h" #include "kis_selection_manager.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "kis_layer.h" #include "kis_selection_options.h" #include "kis_paint_device.h" #include "kis_fill_painter.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_slider_spin_box.h" #include "kis_paint_device.h" #include "kis_pixel_selection.h" #include "tiles3/kis_hline_iterator.h" KisToolSelectContiguous::KisToolSelectContiguous(KoCanvasBase *canvas) : KisToolSelectBase(canvas, KisCursor::load("tool_contiguous_selection_cursor.png", 6, 6), i18n("Contiguous Area Selection")), m_fuzziness(20), m_sizemod(0), m_feather(0), m_limitToCurrentLayer(false) { setObjectName("tool_select_contiguous"); connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged, this, &KisToolSelectContiguous::setSelectionAction); } KisToolSelectContiguous::~KisToolSelectContiguous() { } void KisToolSelectContiguous::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolSelectContiguous::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); KisPaintDeviceSP dev; if (!currentNode() || !(dev = currentNode()->projection()) || !currentNode()->visible() || !selectionEditable()) { event->ignore(); return; } QApplication::setOverrideCursor(KisCursor::waitCursor()); QPoint pos = convertToIntPixelCoord(event); QRect rc = currentImage()->bounds(); KisFillPainter fillpainter(dev); fillpainter.setHeight(rc.height()); fillpainter.setWidth(rc.width()); fillpainter.setFillThreshold(m_fuzziness); fillpainter.setFeather(m_feather); fillpainter.setSizemod(m_sizemod); KisImageWSP image = currentImage(); KisPaintDeviceSP sourceDevice = m_limitToCurrentLayer ? dev : image->projection(); image->lock(); KisSelectionSP selection = fillpainter.createFloodSelection(pos.x(), pos.y(), sourceDevice); image->unlock(); // If we're not antialiasing, threshold the entire selection if (!antiAliasSelection()) { QRect r = selection->selectedExactRect(); if (r.isValid()) { KisHLineIteratorSP selectionIt = selection->pixelSelection()->createHLineIteratorNG(r.x(), r.y(), r.width()); for (qint32 y = 0; y < r.height(); y++) { do { if (selectionIt->rawData()[0] > 0) { selection->pixelSelection()->colorSpace()->setOpacity(selectionIt->rawData(), OPACITY_OPAQUE_U8, 1); } } while (selectionIt->nextPixel()); selectionIt->nextRow(); } } } KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (!kisCanvas || !selection->pixelSelection()) { QApplication::restoreOverrideCursor(); return; } selection->pixelSelection()->invalidateOutlineCache(); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Contiguous Area")); helper.selectPixelSelection(selection->pixelSelection(), selectionAction()); QApplication::restoreOverrideCursor(); } void KisToolSelectContiguous::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisToolSelectContiguous::slotSetFuzziness(int fuzziness) { m_fuzziness = fuzziness; m_configGroup.writeEntry("fuzziness", fuzziness); } void KisToolSelectContiguous::slotSetSizemod(int sizemod) { m_sizemod = sizemod; m_configGroup.writeEntry("sizemod", sizemod); } void KisToolSelectContiguous::slotSetFeather(int feather) { m_feather = feather; m_configGroup.writeEntry("feather", feather); } QWidget* KisToolSelectContiguous::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); selectionWidget->disableSelectionModeOption(); QVBoxLayout * l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); if (l) { - QHBoxLayout * hbox = new QHBoxLayout(); - Q_CHECK_PTR(hbox); - l->insertLayout(1, hbox); + + QGridLayout * gridLayout = new QGridLayout(); + l->insertLayout(1, gridLayout); QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget); - hbox->addWidget(lbl); + gridLayout->addWidget(lbl, 0, 0, 1, 1); KisSliderSpinBox *input = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(input); input->setObjectName("fuzziness"); input->setRange(1, 100); input->setSingleStep(1); input->setExponentRatio(2); - hbox->addWidget(input); - - hbox = new QHBoxLayout(); - Q_CHECK_PTR(hbox); - l->insertLayout(2, hbox); + gridLayout->addWidget(input, 0, 1, 1, 1); lbl = new QLabel(i18n("Grow/shrink selection: "), selectionWidget); - hbox->addWidget(lbl); + gridLayout->addWidget(lbl, 1, 0, 1, 1); KisSliderSpinBox *sizemod = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(sizemod); sizemod->setObjectName("sizemod"); //grow/shrink selection sizemod->setRange(-40, 40); sizemod->setSingleStep(1); - hbox->addWidget(sizemod); - - hbox = new QHBoxLayout(); - Q_CHECK_PTR(hbox); - l->insertLayout(3, hbox); + gridLayout->addWidget(sizemod, 1, 1, 1, 1); - hbox->addWidget(new QLabel(i18n("Feathering radius: "), selectionWidget)); + lbl = new QLabel(i18n("Feathering radius: "), selectionWidget); + gridLayout->addWidget(lbl, 2, 0, 1, 1); KisSliderSpinBox *feather = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(feather); feather->setObjectName("feathering"); feather->setRange(0, 40); feather->setSingleStep(1); - hbox->addWidget(feather); + gridLayout->addWidget(feather, 2, 1, 1, 1); connect (input , SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int) )); connect (sizemod, SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int) )); connect (feather, SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int) )); QCheckBox* limitToCurrentLayer = new QCheckBox(i18n("Limit to current layer"), selectionWidget); l->insertWidget(4, limitToCurrentLayer); connect (limitToCurrentLayer, SIGNAL(stateChanged(int)), this, SLOT(slotLimitToCurrentLayer(int))); // load configuration settings into tool options input->setValue(m_configGroup.readEntry("fuzziness", 20)); // fuzziness sizemod->setValue( m_configGroup.readEntry("sizemod", 0)); //grow/shrink sizemod->setSuffix(i18n(" px")); feather->setValue(m_configGroup.readEntry("feather", 0)); feather->setSuffix(i18n(" px")); limitToCurrentLayer->setChecked(m_configGroup.readEntry("limitToCurrentLayer", false)); } return selectionWidget; } void KisToolSelectContiguous::slotLimitToCurrentLayer(int state) { if (state == Qt::PartiallyChecked) return; m_limitToCurrentLayer = (state == Qt::Checked); m_configGroup.writeEntry("limitToCurrentLayer", state); } void KisToolSelectContiguous::setSelectionAction(int action) { changeSelectionAction(action); } diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui index 42c99afe2f..5fe5de96b5 100644 --- a/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui +++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui @@ -1,148 +1,162 @@ KisToolSmartPatchOptionsWidget 0 0 - 340 - 235 + 250 + 129 - + - - - - 200 - 200 - - - - Inpaint Tool - - - - true - - - - 20 - 40 - 281 - 81 - - - - - 200 - 80 - - + + + + + + + + + low/fast + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::LeftToRight + + + high/slow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + - - - 10 - 16 - 260 - 21 - + + + 0 + 0 + 25 25 50 Qt::Horizontal QSlider::TicksBelow 25 - - - - 190 - 40 - 71 - 20 - - - - Qt::LeftToRight + + + + + + 0 + 0 + + + + Accuracy: + + + + + + + + + + + + 0 + 0 + - Accurate + Patch Radius: + + + patchRadiusLabel + + + + + + + + 0 + 0 + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - 20 - 40 - 41 - 17 - + + px - - Fast + + 2 + + + 8 + + + 4 - - - - - 20 - 153 - 181 - 27 - - - - Patch Radius - - - label - - - - - - 220 - 153 - 61 - 27 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 2 - - - 8 - - - 4 - - - + + + + + + + Qt::Vertical + + + + 20 + 40 + + +