diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 34b18bc159..83e15a8bf7 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,133 +1,164 @@ project (krita-and-all-its-deps) # # Build all dependencies for Krita and finally Krita itself. # Parameters: EXTERNALS_DOWNLOAD_DIR place to download all packages # INSTALL_ROOT place to install everything to # MXE_TOOLCHAIN: the toolchain file to cross-compile using MXE # # Example usage: cmake ..\kritadeposx -DEXTERNALS_DOWNLOAD_DIR=/dev2/d -DINSTALL_ROOT=/dev2/i -DWIN64_BUILD=TRUE -DBOOST_LIBRARYDIR=/dev2/i/lib -G "Visual Studio 11 Win64" cmake_minimum_required(VERSION 2.8.6) if(NOT SUBMAKE_JOBS) set(SUBMAKE_JOBS 1) endif(NOT SUBMAKE_JOBS) if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "Compiling in the source directory is not supported. Use for example 'mkdir build; cd build; cmake ..'.") endif (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) # Tools must be obtained to work with: include (ExternalProject) # allow specification of a directory with pre-downloaded # requirements if(NOT IS_DIRECTORY ${EXTERNALS_DOWNLOAD_DIR}) message(FATAL_ERROR "No externals download dir set. Use -DEXTERNALS_DOWNLOAD_DIR") endif() if(NOT IS_DIRECTORY ${INSTALL_ROOT}) message(FATAL_ERROR "No install dir set. Use -DINSTALL_ROOT") endif() set(TOP_INST_DIR ${INSTALL_ROOT}) set(EXTPREFIX "${TOP_INST_DIR}") set(CMAKE_PREFIX_PATH "${EXTPREFIX}") if (${CMAKE_GENERATOR} STREQUAL "Visual Studio 14 2015 Win64") SET(GLOBAL_PROFILE -DCMAKE_MODULE_LINKER_FLAGS=/machine:x64 -DCMAKE_EXE_LINKER_FLAGS=/machine:x64 -DCMAKE_SHARED_LINKER_FLAGS=/machine:x64 -DCMAKE_STATIC_LINKER_FLAGS=/machine:x64 ) endif () message( STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}") message( STATUS "CMAKE_CL_64: ${CMAKE_CL_64}") set(GLOBAL_BUILD_TYPE RelWithDebInfo) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DBUILD_TESTING=false) +if (MINGW) + option(QT_ENABLE_DEBUG_INFO "Build Qt with debug info included" OFF) +endif (MINGW) + +set(SECURITY_EXE_LINKER_FLAGS "") +set(SECURITY_SHARED_LINKER_FLAGS "") +set(SECURITY_MODULE_LINKER_FLAGS "") +if (MINGW) + option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) + if (USE_MINGW_HARDENING_LINKER) + set(SECURITY_EXE_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") + set(SECURITY_SHARED_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") + set(SECURITY_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(SECURITY_EXE_LINKER_FLAGS "${SECURITY_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") + set(SECURITY_SHARED_LINKER_FLAGS "${SECURITY_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") + set(SECURITY_MODULE_LINKER_FLAGS "${SECURITY_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") + set(GLOBAL_PROFILE ${GLOBAL_PROFILE} + -DCMAKE_EXE_LINKER_FLAGS=${SECURITY_EXE_LINKER_FLAGS} + -DCMAKE_SHARED_LINKER_FLAGS=${SECURITY_SHARED_LINKER_FLAGS} + -DCMAKE_MODULE_LINKER_FLAGS=${SECURITY_MODULE_LINKER_FLAGS} + ) + 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) + if (DEFINED EP_PREFIX) set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX}) endif (DEFINED EP_PREFIX) if (MSVC) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=/PROFILE -DCMAKE_SHARED_LINKER_FLAGS=/PROFILE) set(PATCH_COMMAND myptch) endif() if (MINGW) set(PATCH_COMMAND myptch) endif() if (MSYS) set(PATCH_COMMAND patch) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_TOOLCHAIN_FILE=${MXE_TOOLCHAIN} -DCMAKE_FIND_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_SYSTEM_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_LIBRARY_PATH=${CMAKE_PREFIX_PATH}/lib -DZLIB_ROOT=${CMAKE_PREFIX_PATH} ) set(GLOBAL_AUTOMAKE_PROFILE --host=i686-pc-mingw32 ) endif() if (APPLE) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_MACOSX_RPATH=ON -DKDE_SKIP_RPATH_SETTINGS=ON -DBUILD_WITH_INSTALL_RPATH=ON -DAPPLE_SUPPRESS_X11_WARNING=ON) set(PATCH_COMMAND patch) endif () if (UNIX AND NOT APPLE) set(LINUX true) set(PATCH_COMMAND patch) endif () # this list must be dependency-ordered add_subdirectory( ext_python ) if (MSVC) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif (MSVC) if (MINGW) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif (MINGW) add_subdirectory( ext_iconv ) add_subdirectory( ext_gettext ) add_subdirectory( ext_zlib ) add_subdirectory( ext_libxml2 ) add_subdirectory( ext_libxslt ) add_subdirectory( ext_boost ) add_subdirectory( ext_jpeg ) add_subdirectory( ext_tiff ) add_subdirectory( ext_png ) add_subdirectory( ext_eigen3 ) add_subdirectory( ext_expat ) # for exiv2 add_subdirectory( ext_exiv2 ) add_subdirectory( ext_ilmbase ) add_subdirectory( ext_lcms2 ) add_subdirectory( ext_openexr ) add_subdirectory( ext_vc ) add_subdirectory( ext_gsl ) add_subdirectory( ext_fftw3 ) add_subdirectory( ext_ocio ) if (MSVC) add_subdirectory( ext_pthreads ) endif (MSVC) add_subdirectory( ext_fontconfig) add_subdirectory( ext_freetype) add_subdirectory( ext_qt ) add_subdirectory( ext_poppler ) add_subdirectory( ext_libraw ) add_subdirectory( ext_frameworks ) add_subdirectory( ext_sip ) add_subdirectory( ext_pyqt ) if (MSVC OR MINGW) add_subdirectory( ext_drmingw ) endif (MSVC OR MINGW) diff --git a/3rdparty/README.md b/3rdparty/README.md index 3f563afd75..c7ce57d3c0 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,245 +1,242 @@ = 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 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 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 or later: 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. + * CMake 3.9 does not build Krita properly at the moment, please use 3.8 instead. 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/ - + * Windows: mingw-w64 7.1 (by mingw-builds) + - 32-bit (x86) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/7.1.0/threads-posix/dwarf/ + - 64-bit (x64) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.1.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. +4. On Windows, you will also need Python 3.6.2 (technically any versions of 3.6 is fine, but it's not tested): https://www.python.org. Make sure to have that version of 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.2. Make sure that specific version of Python comes first 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. run cmake: * Linux: export PATH=$BUILDROOT/i/bin:$PATH 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:$PATH 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 + -DINSTALL_ROOT=$BUILDROOT/i - * Windows 32 bits: - - TODO - - * Windows 64 bits: + * Windows 32-bit / 64-bit: 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" +- If you want to build Qt and some other dependencies with parallel jobs, add + `-DSUBMAKE_JOBS=` to the cmake command where is the number of jobs to + run (if your PC has 4 CPU cores, you might want to set it to 5). + 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 OSX: 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 +On Windows, if you want to include Python scripting: + + cmake --build . --config RelWithDebInfo --target ext_python + cmake --build . --config RelWithDebInfo --target ext_sip + cmake --build . --config RelWithDebInfo --target ext_pyqt + 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: + +1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build -3. Run +3. Run On Windows -Depending on what you want to use, run this command for MSBuild: +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 +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 package_2.cmd file from the "windows" folder inside krita root source folder to BUILDROOT and run it (most likely C:\dev\). -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. +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. +>>>>>>> origin/master diff --git a/3rdparty/ext_boost/CMakeLists.txt b/3rdparty/ext_boost/CMakeLists.txt index 72a580b8bb..e20ea14060 100755 --- a/3rdparty/ext_boost/CMakeLists.txt +++ b/3rdparty/ext_boost/CMakeLists.txt @@ -1,86 +1,86 @@ SET(PREFIX_ext_boost "${EXTPREFIX}" ) if (MSVC) if (${CMAKE_GENERATOR} STREQUAL "Visual Studio 14 2015 Win64") ExternalProject_Add( ext_boost DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/boost_1_61_0.zip URL_MD5 015ae4afa6f3e597232bfe1dab949ace CONFIGURE_COMMAND /bootstrap.bat --prefix=${PREFIX_ext_boost} BUILD_COMMAND /b2.exe -j${SUBMAKE_JOBS} --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=msvc-14.0 variant=release link=shared threading=multi architecture=x86 address-model=64 variant=release install INSTALL_COMMAND "" INSTALL_DIR ${EXTPREFIX_boost} UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) ExternalProject_Add_Step( ext_boost post_install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_boost}/lib/boost_system-vc140-mt-1_61.dll ${PREFIX_ext_boost}/bin/boost_system-vc140-mt-1_61.dll DEPENDEES install ) else() ExternalProject_Add( ext_boost DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/boost_1_61_0.zip URL_MD5 015ae4afa6f3e597232bfe1dab949ace CONFIGURE_COMMAND /bootstrap.bat --prefix=${PREFIX_ext_boost} BUILD_COMMAND /b2.exe -j${SUBMAKE_JOBS} --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=msvc-14.0 variant=release link=shared threading=multi architecture=x86 variant=release install INSTALL_COMMAND "" INSTALL_DIR ${EXTPREFIX_boost} UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) ExternalProject_Add_Step( ext_boost post_install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_boost}/lib/boost_system-vc140-mt-1_61.dll ${PREFIX_ext_boost}/bin/boost_system-vc140-mt-1_61.dll DEPENDEES install ) endif() elseif(MINGW) string(REGEX REPLACE "([0-9])\\.([0-9])(\\.[0-9])?" "\\1\\2" KRITA_boost_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) ExternalProject_Add( ext_boost DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/boost_1_61_0.zip URL_MD5 015ae4afa6f3e597232bfe1dab949ace CONFIGURE_COMMAND /bootstrap.bat gcc --prefix=${PREFIX_ext_boost} - BUILD_COMMAND /b2.exe -j${SUBMAKE_JOBS} --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=gcc variant=release link=shared threading=multi architecture=x86 variant=release install + BUILD_COMMAND /b2.exe -j${SUBMAKE_JOBS} linkflags=${SECURITY_SHARED_LINKER_FLAGS} --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=gcc variant=release link=shared threading=multi architecture=x86 variant=release install INSTALL_COMMAND "" INSTALL_DIR ${EXTPREFIX_boost} UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) ExternalProject_Add_Step( ext_boost post_install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_boost}/lib/libboost_system-mgw${KRITA_boost_COMPILER_VERSION}-mt-1_61.dll ${PREFIX_ext_boost}/bin/ DEPENDEES install ) else() ExternalProject_Add( ext_boost DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/boost_1_61_0.tar.bz2 URL_MD5 6095876341956f65f9d35939ccea1a9f CONFIGURE_COMMAND /bootstrap.sh --prefix=${PREFIX_ext_boost} --with-libraries=system BUILD_COMMAND /b2 -j${SUBMAKE_JOBS} install INSTALL_COMMAND "" INSTALL_DIR ${PREFIX_ext_boost} UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/3rdparty/ext_fftw3/libfftw.py b/3rdparty/ext_fftw3/libfftw.py index f2c4d9f921..bfc29a3da3 100644 --- a/3rdparty/ext_fftw3/libfftw.py +++ b/3rdparty/ext_fftw3/libfftw.py @@ -1,25 +1,28 @@ # -*- coding: utf-8 -*- import info -class subinfo( info.infoclass ): - def setTargets( self ): - self.targets[ '3.2.2' ] = 'http://www.fftw.org/fftw-3.2.2.tar.gz' - self.targetDigests[ '3.2.2' ] = 'd43b799eedfb9408f62f9f056f5e8a645618467b' - self.targetInstSrc[ '3.2.2' ] = "fftw-3.2.2" - self.patchToApply[ '3.2.2' ] = [ ( 'fftw-3.2.2-20111221.diff', 1 ), - ( 'fftw-3.2.2-20130818.diff', 1 ) ] + +class subinfo(info.infoclass): + + def setTargets(self): + self.targets['3.2.2'] = 'http://www.fftw.org/fftw-3.2.2.tar.gz' + self.targetDigests['3.2.2'] = 'd43b799eedfb9408f62f9f056f5e8a645618467b' + self.targetInstSrc['3.2.2'] = "fftw-3.2.2" + self.patchToApply['3.2.2'] = [('fftw-3.2.2-20111221.diff', 1), + ('fftw-3.2.2-20130818.diff', 1)] self.shortDescription = "a C subroutine library for computing the discrete Fourier transform (DFT)" self.defaultTarget = '3.2.2' - def setDependencies( self ): - self.buildDependencies[ 'virtual/base' ] = 'default' + def setDependencies(self): + self.buildDependencies['virtual/base'] = 'default' from Package.CMakePackageBase import * -class Package( CMakePackageBase ): - def __init__( self ): - CMakePackageBase.__init__( self ) + +class Package(CMakePackageBase): + + def __init__(self): + CMakePackageBase.__init__(self) self.supportsNinja = False self.subinfo.options.configure.defines = "-DFFTW_SINGLE=ON -DFFTW_DOUBLE=OFF -DBUILD_BENCHMARKS=OFF" - diff --git a/3rdparty/ext_frameworks/CMakeLists.txt b/3rdparty/ext_frameworks/CMakeLists.txt index 4810deb6e2..203ceb9747 100755 --- a/3rdparty/ext_frameworks/CMakeLists.txt +++ b/3rdparty/ext_frameworks/CMakeLists.txt @@ -1,226 +1,227 @@ SET(EXTPREFIX_frameworks "${EXTPREFIX}" ) # # All needed frameworks: # # Archive # Config # WidgetsAddons # Completion # CoreAddons # GuiAddons # I18n # ItemModels # ItemViews # WindowSystem # On Linux: # KCrash ExternalProject_Add( ext_extra_cmake_modules DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/extra-cmake-modules-5.24.0.zip URL_MD5 e0c19ba97ebd964f9bdc9110c64ce96a PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ecm_install_to_share.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" ) ExternalProject_Add( ext_karchive DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/karchive-5.24.0.zip URL_MD5 739843accfe9bd85ab2f1582722cf01e INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_extra_cmake_modules ) ExternalProject_Add( ext_kconfig DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kconfig-5.24.0.zip URL_MD5 f87ecff795eb76e4ec6561758a5baf87 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kconfig.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_karchive ) ExternalProject_Add( ext_kwidgetsaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kwidgetsaddons-5.24.0.zip URL_MD5 0e399b427814a4814c65a3cf407f9d79 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwidgetsaddons.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kconfig ) ExternalProject_Add( ext_kcompletion DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kcompletion-5.24.0.zip URL_MD5 e8764251ab45005aa81dba242852300c INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kwidgetsaddons ) ExternalProject_Add( ext_kcoreaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kcoreaddons-5.24.0.zip URL_MD5 2885878625b19ad0300ef3770b897112 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/desktoptojson.diff + COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kcoreaddons-compile-fix.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kcompletion ) ExternalProject_Add( ext_kguiaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kguiaddons-5.24.0.zip URL_MD5 9bdadbc57d0634816ef80ee9798c3d6c INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kcoreaddons ) ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.17/ki18n-5.17.0.zip URL_MD5 7d60380d09f98defbf878ea9daba0fbb INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ki18n.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ki18n-appdatalocation.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kguiaddons ) ExternalProject_Add( ext_kitemmodels DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kitemmodels-5.24.0.zip URL_MD5 ff41589f48395fc01d5fc7887593779d INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_ki18n ) ExternalProject_Add( ext_kitemviews DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kitemviews-5.24.0.zip URL_MD5 33f638d027a3011a6a69f7484eee3287 INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kitemmodels ) ExternalProject_Add( ext_kimageformats DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kimageformats-5.24.0.zip URL_MD5 c1964516bcb2bfe882858f0c0913deb5 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kimageformats.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kitemviews ) ExternalProject_Add( ext_kwindowsystem DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kwindowsystem-5.24.0.zip URL_MD5 5915e4f63ded983af6db7db3a6cbae1a INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwindowsystem-x11.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kimageformats ) ExternalProject_Add( ext_kcrash DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/Attic/frameworks/5.24/kcrash-5.24.0.zip URL_MD5 a2e41e6650105fc3ac8fbd44afbae4fe INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kwindowsystem ) diff --git a/3rdparty/ext_frameworks/kcoreaddons-compile-fix.diff b/3rdparty/ext_frameworks/kcoreaddons-compile-fix.diff new file mode 100644 index 0000000000..08fc26605c --- /dev/null +++ b/3rdparty/ext_frameworks/kcoreaddons-compile-fix.diff @@ -0,0 +1,19 @@ +commit e0ea4199bc4da359eb91ab51274785d17a4f2909 +Author: Ralf Habacker +Date: Wed Sep 21 13:38:03 2016 +0200 + + Windows compile fix. + +diff --git a/src/lib/util/kuser_win.cpp b/src/lib/util/kuser_win.cpp +index 1d77f89..5c3fa45 100644 +--- a/src/lib/util/kuser_win.cpp ++++ b/src/lib/util/kuser_win.cpp +@@ -853,7 +853,7 @@ static std::unique_ptr queryProcessInformation(TOKEN_INFORMATION_CLASS t + HANDLE _token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &_token)) { + qWarning("Failed to get the token for the current process: %d", (int)GetLastError()); +- return false; ++ return nullptr; + } + ScopedHANDLE token(_token, handleCloser); + // query required size diff --git a/3rdparty/ext_pyqt/CMakeLists.txt b/3rdparty/ext_pyqt/CMakeLists.txt index 6b15924903..8e429e960c 100644 --- a/3rdparty/ext_pyqt/CMakeLists.txt +++ b/3rdparty/ext_pyqt/CMakeLists.txt @@ -1,34 +1,47 @@ SET(PREFIX_ext_pyqt "${EXTPREFIX}" ) if (UNIX) ExternalProject_Add( ext_pyqt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/PyQt5_gpl-5.6.tar.gz URL_MD5 dbfc885c0548e024ba5260c4f44e0481 CONFIGURE_COMMAND ${PREFIX_ext_pyqt}/bin/python3 /configure.py --confirm-license BUILD_COMMAND make INSTALL_COMMAND make install BUILD_IN_SOURCE 1 UPDATE_COMMAND "" - ALWAYS 0 ) elseif(MINGW) + list(APPEND _PYQT_conf + --confirm-license + --target-py-version 3.6 + --bindir ${PREFIX_ext_pyqt}/bin + --qt ${PREFIX_ext_pyqt} + --sip ${PREFIX_ext_pyqt}/bin/sip.exe + --sip-incdir ${PREFIX_ext_pyqt}/include + --spec win32-g++ + --verbose + --sipdir ${PREFIX_ext_pyqt}/share/sip + --destdir ${PREFIX_ext_pyqt}/share/krita/pykrita + --no-qml-plugin --no-python-dbus --no-qsci-api --no-tools + --disable QtSql --disable QtTest --disable QtWinExtras + ) ExternalProject_Add( ext_pyqt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/PyQt5_gpl-5.6.zip - URL_MD5 916e79bacda1799f8db7bc034043e450 - - CONFIGURE_COMMAND python.exe /configure.py --confirm-license --target-py-version 3.6 --bindir {PREFIX_ext_pyqt}/bin --qt ${PREFIX_ext_pyqt} --sip ${PREFIX_ext_pyqt}/bin/sip.exe --sip-incdir ${PREFIX_ext_pyqt}/include --target-py-version 3.6 --spec win32-g++ --verbose --sipdir ${PREFIX_ext_pyqt}/share/sip --destdir ${PREFIX_ext_pyqt}/bin --no-qml-plugin --no-python-dbus --no-qsci-api --no-tools - BUILD_COMMAND mingw32-make - INSTALL_COMMAND mingw32-make install + URL https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.9/PyQt5_gpl-5.9.zip + URL_MD5 d978884753df265896eda436d8f4e07b + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/pyqt-configure-fix.patch + + CONFIGURE_COMMAND python.exe /configure.py ${_PYQT_conf} + BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} CXXFLAGS=-D_hypot=hypot LDFLAGS=${SECURITY_SHARED_LINKER_FLAGS} + INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install BUILD_IN_SOURCE 1 UPDATE_COMMAND "" - ALWAYS 0 ) endif() diff --git a/3rdparty/ext_pyqt/pyqt-configure-fix.patch b/3rdparty/ext_pyqt/pyqt-configure-fix.patch new file mode 100644 index 0000000000..86d896664e --- /dev/null +++ b/3rdparty/ext_pyqt/pyqt-configure-fix.patch @@ -0,0 +1,15 @@ +--- a/configure.py 2017-07-03 18:25:14.000000000 +0800 ++++ b/configure.py 2017-07-15 01:19:44.622435500 +0800 +@@ -2168,10 +2168,8 @@ + if source is None: + for disabled in run_test_program(mname, test, verbose): + if disabled: +- inform("Disabled %s features: %s" % (mname, +- ', '.join(disabled))) +- +- target_config.pyqt_disabled_features.extend(disabled) ++ inform("Disabled %s features: %s" % (mname, disabled)) ++ target_config.pyqt_disabled_features.append(disabled) + + # Include the module in the build. + target_config.pyqt_modules.append(mname) diff --git a/3rdparty/ext_python/CMakeLists.txt b/3rdparty/ext_python/CMakeLists.txt index 708924e323..c0e99c2a74 100644 --- a/3rdparty/ext_python/CMakeLists.txt +++ b/3rdparty/ext_python/CMakeLists.txt @@ -1,46 +1,47 @@ SET(PREFIX_ext_python "${EXTPREFIX}" ) if (UNIX) ExternalProject_Add( ext_python DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/Python-3.5.2.tar.gz URL_MD5 ea334d398990037a4b8be324bd475c83 CONFIGURE_COMMAND /configure --prefix=${PREFIX_ext_python} ${GLOBAL_AUTOMAKE_PROFILE} --enable-shared BUILD_COMMAND make INSTALL_COMMAND make install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_python}/bin/python3 ${PREFIX_ext_python}/bin/python UPDATE_COMMAND "" ALWAYS 0 ) elseif(MINGW) if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") ExternalProject_Add( ext_python DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/python-3.6.1-embed-amd64.zip - URL_MD5 708496ebbe9a730d19d5d288afd216f1 + URL https://www.python.org/ftp/python/3.6.2/python-3.6.2-embed-amd64.zip + URL_MD5 0fdfe9f79e0991815d6fc1712871c17f INSTALL_DIR ${PREFIX_ext_python} CONFIGURE_COMMAND "" BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying python3 64-bit binary - INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory / ${PREFIX_ext_python}/bin + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory / ${PREFIX_ext_python}/python + COMMAND ${CMAKE_COMMAND} -E copy /python3.dll ${PREFIX_ext_python}/bin + COMMAND ${CMAKE_COMMAND} -E copy /python36.dll ${PREFIX_ext_python}/bin UPDATE_COMMAND "" ) else("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - ExternalProject_Add( ext_python DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/python-3.6.1-embed-win32.zip - URL_MD5 8dff09a1b19b7a7dcb915765328484cf + URL https://www.python.org/ftp/python/3.6.2/python-3.6.2-embed-win32.zip + URL_MD5 2ca4768fdbadf6e670e97857bfab83e8 INSTALL_DIR ${PREFIX_ext_python} CONFIGURE_COMMAND "" BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying python3 32-bit binary - INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory / ${PREFIX_ext_python}/bin - - + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory / ${PREFIX_ext_python}/python + COMMAND ${CMAKE_COMMAND} -E copy /python3.dll ${PREFIX_ext_python}/bin + COMMAND ${CMAKE_COMMAND} -E copy /python36.dll ${PREFIX_ext_python}/bin UPDATE_COMMAND "" ) endif("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") endif() diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index ba606e1233..6cca1a11d2 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,183 +1,206 @@ SET(EXTPREFIX_qt "${EXTPREFIX}") if (WIN32) + list(APPEND _QT_conf -skip qt3d -skip qtactiveqt -skip qtcanvas3d + -skip qtconnectivity -skip qtdoc -skip qtenginio -skip qtgraphicaleffects + -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland + -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview + -skip qtxmlpatterns -no-sql-sqlite -nomake examples -nomake tools + -no-compile-examples -no-dbus -no-iconv -no-angle -no-qml-debug -no-ssl + -no-openssl -no-libproxy -no-system-proxies -no-icu -no-mtdev + -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth + -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus + -skip qtspeech -skip qtvirtualkeyboard + # + -opengl desktop -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg + # + -opensource -confirm-license + # + -release -platform win32-g++ -prefix ${EXTPREFIX_qt} + QMAKE_LFLAGS_APP+=${SECURITY_EXE_LINKER_FLAGS} + QMAKE_LFLAGS_SHLIB+=${SECURITY_SHARED_LINKER_FLAGS} + QMAKE_LFLAGS_SONAME+=${SECURITY_SHARED_LINKER_FLAGS} + ) + if (QT_ENABLE_DEBUG_INFO) + # Set the option to build Qt with debugging info enabled + list(APPEND _QT_conf -force-debug-info) + endif(QT_ENABLE_DEBUG_INFO) 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 + URL https://download.qt.io/archive/qt/5.9/5.9.1/single/qt-everywhere-opensource-src-5.9.1.zip + URL_MD5 3f2e538ccc468d28bcfdefac96d1e975 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++ + CONFIGURE_COMMAND /configure.bat ${_QT_conf} BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install UPDATE_COMMAND "" - BUILD_IN_SOURCE 1 + # Use a short name to reduce the chance of exceeding path length limit + SOURCE_DIR s + BINARY_DIR b DEPENDS ext_patch ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.6/5.6.1-1/single/qt-everywhere-opensource-src-5.6.1-1.tar.gz URL_MD5 8fdec6d657bc370bd3183d8fe8e9c47a PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qt-no-motion-compression.diff INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -nomake examples -no-sql-sqlite -no-openssl -no-qml-debug -no-mtdev -no-journald -no-syslog -no-nis -no-cups -no-tslib -no-directfb -no-linuxfb -no-libproxy -no-pch -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -qt-harfbuzz -qt-freetype -qt-xcb -qt-xkbcommon-x11 -optimized-qmake -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtenginio -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) else( APPLE ) # XCODE_VERSION is set by CMake when using the Xcode generator, otherwise we need # to detect it manually here. if (NOT XCODE_VERSION) execute_process( COMMAND xcodebuild -version OUTPUT_VARIABLE xcodebuild_version OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_FILE /dev/null ) string(REGEX MATCH "Xcode ([0-9]([.][0-9])+)" version_match ${xcodebuild_version}) if (version_match) message(STATUS "${EXTPREFIX_qt}:Identified Xcode Version: ${CMAKE_MATCH_1}") set(XCODE_VERSION ${CMAKE_MATCH_1}) else() # If detecting Xcode version failed, set a crazy high version so we default # to the newest. set(XCODE_VERSION 99) message(WARNING "${EXTPREFIX_qt}:Failed to detect the version of an installed copy of Xcode, falling back to highest supported version. Set XCODE_VERSION to override.") endif(version_match) endif(NOT XCODE_VERSION) # ------------------------------------------------------------------------------- # Verify the Xcode installation on Mac OS like Qt5.7 does/will # If not stop now, the system isn't configured correctly for Qt. # No reason to even proceed. # ------------------------------------------------------------------------------- set(XCSELECT_OUTPUT) find_program(XCSELECT_PROGRAM "xcode-select") if(XCSELECT_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCSELECT_PROGRAM as ${XCSELECT_PROGRAM}") set(XCSELECT_COMMAND ${XCSELECT_PROGRAM} "--print-path") execute_process( COMMAND ${XCSELECT_COMMAND} RESULT_VARIABLE XCSELECT_COMMAND_RESULT OUTPUT_VARIABLE XCSELECT_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCSELECT_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCSELECT_COMMAND_OUTPUT ${XCSELECT_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCSELECT_COMMAND_STR "${XCSELECT_COMMAND}") # message(STATUS "${XCSELECT_COMMAND_STR}") message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} test failed with status ${XCSELECT_COMMAND_RESULT}") endif() else() message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} not found. No Xcode is selected. Use xcode-select -switch to choose an Xcode version") endif() # Belts and suspenders # Beyond all the Xcode and Qt version checking, the proof of the pudding # lies in the success/failure of this command: xcrun --find xcrun. # On failure a patch is necessary, otherwise we're ok # So hard check xcrun now... set(XCRUN_OUTPUT) find_program(XCRUN_PROGRAM "xcrun") if(XCRUN_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCRUN_PROGRAM as ${XCRUN_PROGRAM}") set(XCRUN_COMMAND ${XCRUN_PROGRAM} "--find xcrun") execute_process( COMMAND ${XCRUN_COMMAND} RESULT_VARIABLE XCRUN_COMMAND_RESULT OUTPUT_VARIABLE XCRUN_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCRUN_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCRUN_COMMAND_OUTPUT ${XCRUN_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCRUN_COMMAND_STR "${XCRUN_COMMAND}") # message(STATUS "${XCRUN_COMMAND_STR}") message(STATUS "${EXTPREFIX_qt}:xcrun test failed with status ${XCRUN_COMMAND_RESULT}") endif() else() message(STATUS "${EXTPREFIX_qt}:xcrun not found -- ${XCRUN_PROGRAM}") endif() # # Now configure ext_qt accordingly # if ((XCRUN_COMMAND_RESULT) AND (NOT (XCODE_VERSION VERSION_LESS 8.0.0))) # Fix Xcode xcrun related issue. # NOTE: This should be fixed by Qt 5.7.1 see here: http://code.qt.io/cgit/qt/qtbase.git/commit/?h=dev&id=77a71c32c9d19b87f79b208929e71282e8d8b5d9 # NOTE: but no one's holding their breath. set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/gerrit-166202.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/qtbase-configure.patch COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase/mkspecs/features/mac -i ${CMAKE_CURRENT_SOURCE_DIR}/mac-default.patch) 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" ) 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/3rdparty/ext_qt/disable-wintab.diff b/3rdparty/ext_qt/disable-wintab.diff index 6a98378662..8484959597 100644 --- a/3rdparty/ext_qt/disable-wintab.diff +++ b/3rdparty/ext_qt/disable-wintab.diff @@ -1,68 +1,68 @@ diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp index 4934b6c..613e8fe 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp -@@ -299,9 +299,6 @@ struct QWindowsContextPrivate { +@@ -238,9 +238,6 @@ QWindowsMimeConverter m_mimeConverter; QWindowsScreenManager m_screenManager; QSharedPointer m_creationContext; --#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +-#if QT_CONFIG(tabletevent) - QScopedPointer m_tabletSupport; -#endif const HRESULT m_oleInitializeResult; const QByteArray m_eventType; - QWindow *m_lastActiveWindow; -@@ -346,17 +343,10 @@ QWindowsContext::QWindowsContext() : + QWindow *m_lastActiveWindow = nullptr; +@@ -279,17 +276,10 @@ const QByteArray bv = qgetenv("QT_QPA_VERBOSE"); if (!bv.isEmpty()) QLoggingCategory::setFilterRules(QString::fromLocal8Bit(bv)); --#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +-#if QT_CONFIG(tabletevent) - d->m_tabletSupport.reset(QWindowsTabletSupport::create()); - qCDebug(lcQpaTablet) << "Tablet support: " << (d->m_tabletSupport.isNull() ? QStringLiteral("None") : d->m_tabletSupport->description()); -#endif } QWindowsContext::~QWindowsContext() { --#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +-#if QT_CONFIG(tabletevent) - d->m_tabletSupport.reset(); // Destroy internal window before unregistering classes. -#endif unregisterWindowClasses(); if (d->m_oleInitializeResult == S_OK || d->m_oleInitializeResult == S_FALSE) OleUninitialize(); -@@ -397,12 +387,7 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) +@@ -335,12 +325,7 @@ void QWindowsContext::setTabletAbsoluteRange(int a) { --#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +-#if QT_CONFIG(tabletevent) - if (!d->m_tabletSupport.isNull()) - d->m_tabletSupport->setAbsoluteRange(a); -#else Q_UNUSED(a) -#endif } int QWindowsContext::processDpiAwareness() -@@ -792,11 +777,7 @@ QWindowsScreenManager &QWindowsContext::screenManager() +@@ -700,11 +685,7 @@ QWindowsTabletSupport *QWindowsContext::tabletSupport() const { --#if !defined(QT_NO_TABLETEVENT) && !defined(Q_OS_WINCE) +-#if QT_CONFIG(tabletevent) - return d->m_tabletSupport.data(); -#else return 0; -#endif } /*! -@@ -1166,10 +1147,6 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, +@@ -1077,10 +1058,6 @@ *result = LRESULT(MA_NOACTIVATE); return true; } -#ifndef QT_NO_TABLETEVENT - if (!d->m_tabletSupport.isNull()) - d->m_tabletSupport->notifyActivate(); -#endif // !QT_NO_TABLETEVENT if (platformWindow->testFlag(QWindowsWindow::BlockedByModal)) - if (const QWindow *modalWindow = QGuiApplication::modalWindow()) - QWindowsWindow::baseWindowOf(modalWindow)->alertWindow(); + if (const QWindow *modalWindow = QGuiApplication::modalWindow()) { + QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(modalWindow); diff --git a/3rdparty/ext_sip/CMakeLists.txt b/3rdparty/ext_sip/CMakeLists.txt index 51dc55274f..6faea99d69 100644 --- a/3rdparty/ext_sip/CMakeLists.txt +++ b/3rdparty/ext_sip/CMakeLists.txt @@ -1,33 +1,39 @@ SET(PREFIX_ext_sip "${EXTPREFIX}" ) if (UNIX) ExternalProject_Add( ext_sip DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/sip-4.18.tar.gz URL_MD5 78724bf2a79878201c3bc81a1d8248ea CONFIGURE_COMMAND ${PREFIX_ext_sip}/bin/python3 /configure.py -b ${PREFIX_ext_sip}/bin -d ${PREFIX_ext_sip}/sip -e ${PREFIX_ext_sip}/include --sipdir ${PREFIX_ext_sip}/sip --target-py-version 3.5 BUILD_COMMAND make INSTALL_COMMAND make install BUILD_IN_SOURCE 1 UPDATE_COMMAND "" - ALWAYS 0 ) elseif (MINGW) + list(APPEND _SIP_conf + --platform win32-g++ + -b ${PREFIX_ext_sip}/bin + -d ${PREFIX_ext_sip}/share/krita/pykrita + -e ${PREFIX_ext_sip}/include + --sipdir ${PREFIX_ext_sip}/share/sip + --target-py-version 3.6 + ) ExternalProject_Add( ext_sip DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/sip-4.18.zip - URL_MD5 e860d06782962fa02f81aeecba3d82a7 - - CONFIGURE_COMMAND python.exe /configure.py --platform win32-g++ -b ${PREFIX_ext_sip}/bin -d ${PREFIX_ext_sip}/sip -e ${PREFIX_ext_sip}/include --sipdir ${PREFIX_ext_sip}/sip --target-py-version 3.6 - BUILD_COMMAND mingw32-make - INSTALL_COMMAND mingw32-make install + URL https://sourceforge.net/projects/pyqt/files/sip/sip-4.19.3/sip-4.19.3.zip + URL_MD5 1098da9ee1915354fedf38fd6fbe22ce + + CONFIGURE_COMMAND python.exe /configure.py ${_SIP_conf} + BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} LDFLAGS=${SECURITY_SHARED_LINKER_FLAGS} + INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install BUILD_IN_SOURCE 1 UPDATE_COMMAND "" - ALWAYS 0 ) endif() diff --git a/3rdparty/ext_vc/CMakeLists.txt b/3rdparty/ext_vc/CMakeLists.txt index 9c218bbfab..0df09a9670 100755 --- a/3rdparty/ext_vc/CMakeLists.txt +++ b/3rdparty/ext_vc/CMakeLists.txt @@ -1,14 +1,12 @@ SET(PREFIX_ext_vc "${EXTPREFIX}" ) ExternalProject_Add( ext_vc DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/Vc-1.2.0.tar.gz - URL_MD5 f2a213ae4bad0dcf4ec6469e4dad41c1 + URL https://github.com/VcDevel/Vc/releases/download/1.3.2/Vc-1.3.2.tar.gz + URL_HASH SHA1=2a87ff94dd18836cd3934b54180b15b3505bd50c INSTALL_DIR ${PREFIX_ext_vc} - PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/vc1.2_malloc_free.patch CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_vc} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PROCESSOR=x86 UPDATE_COMMAND "" ) - diff --git a/3rdparty/ext_vc/vc1.2_malloc_free.patch b/3rdparty/ext_vc/vc1.2_malloc_free.patch deleted file mode 100644 index b257bd3dac..0000000000 --- a/3rdparty/ext_vc/vc1.2_malloc_free.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/common/malloc.h -+++ b/common/malloc.h -@@ -90,6 +90,12 @@ - { - #ifdef __MIC__ - _mm_free(p); -+#elif defined(_WIN32) -+# ifdef __GNUC__ -+ return __mingw_aligned_free(p); -+# else -+ return _aligned_free(p); -+# endif - #else - std::free(p); - #endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 02cc6c8126..3209cb1f66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,665 +1,675 @@ 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) - +if(MINGW) + # Special check: Building on Windows and ext_python is used + find_package(PythonInterp 3.6) + if(PYTHONINTERP_FOUND) + find_package(PythonLibrary 3.6) + if(PYTHONLIBS_FOUND) + include("${CMAKE_CURRENT_SOURCE_DIR}/PythonWindowsCheck.cmake") + endif(PYTHONLIBS_FOUND) + endif(PYTHONINTERP_FOUND) +else(MINGW) + find_package(PythonInterp 3.0) + find_package(PythonLibrary 3.0) +endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.19 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Archive Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) 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}) + find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) find_package(XCB COMPONENTS XCB ATOM) set(HAVE_XCB ${XCB_FOUND}) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) set(HAVE_XCB FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -DQT_NO_URL_CAST_FROM_STRING -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 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 +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/PythonWindowsCheck.cmake b/PythonWindowsCheck.cmake new file mode 100644 index 0000000000..a6f768abaf --- /dev/null +++ b/PythonWindowsCheck.cmake @@ -0,0 +1,23 @@ +# Check whether the found python is the same as ext_python +# HACK: Find pythonxx.dll and compare equality. Probably not the best idea... +# TODO: Check the python version + +set(_check_python_dll "python36.dll") + +if(NOT ${PYTHONLIBS_VERSION_STRING} VERSION_EQUAL "3.6.2") + message(FATAL_ERROR "Windows build with Python requires Python 3.6.2, found version ${PYTHONLIBS_VERSION_STRING} instead.") +else() + if(EXISTS "${CMAKE_INSTALL_PREFIX}/python/${_check_python_dll}") + message(STATUS "python36.dll is found in \"${CMAKE_INSTALL_PREFIX}/python/\".") + file(SHA1 "${CMAKE_INSTALL_PREFIX}/python/${_check_python_dll}" _ext_python_dll_sha1) + get_filename_component(_found_python_dir ${PYTHON_EXECUTABLE} DIRECTORY) + file(SHA1 "${_found_python_dir}/${_check_python_dll}" _found_python_dll_sha1) + if(NOT ${_ext_python_dll_sha1} STREQUAL ${_found_python_dll_sha1}) + message(FATAL_ERROR "The found ${_check_python_dll} is not the same as the ${_check_python_dll} from ext_python.") + endif() + else() + message(FATAL_ERROR "${_check_python_dll} is NOT found in \"${CMAKE_INSTALL_PREFIX}/python/\".") + endif() +endif() + +unset(_check_python_dll) diff --git a/benchmarks/kis_projection_benchmark.cpp b/benchmarks/kis_projection_benchmark.cpp index 1bb98f2d0d..a4649f5934 100644 --- a/benchmarks/kis_projection_benchmark.cpp +++ b/benchmarks/kis_projection_benchmark.cpp @@ -1,67 +1,62 @@ /* * 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() { - 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(); + QBENCHMARK{ + KisDocument *doc = KisPart::instance()->createDocument(); + doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"); + doc->image()->refreshGraph(); + doc->exportDocumentSync(QUrl::fromLocalFile(QString(FILES_OUTPUT_DIR) + QDir::separator() + "save_test.kra"), doc->mimeType()); + delete doc; } - - 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/FindPyQt5.cmake b/cmake/modules/FindPyQt5.cmake index b4a52e156c..f4b142cb4d 100644 --- a/cmake/modules/FindPyQt5.cmake +++ b/cmake/modules/FindPyQt5.cmake @@ -1,53 +1,54 @@ # Find PyQt5 # ~~~~~~~~~~ # Copyright (c) 2014, Simon Edwards # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # # PyQt5 website: http://www.riverbankcomputing.co.uk/pyqt/index.php # # Find the installed version of PyQt5. FindPyQt5 should only be called after # Python has been found. # # This file defines the following variables: # # PYQT5_VERSION - The version of PyQt5 found expressed as a 6 digit hex number # suitable for comparison as a string # # PYQT5_VERSION_STR - The version of PyQt5 as a human readable string. # # PYQT5_VERSION_TAG - The PyQt version tag using by PyQt's sip files. # # PYQT5_SIP_DIR - The directory holding the PyQt5 .sip files. # # PYQT5_SIP_FLAGS - The SIP flags used to build PyQt. IF(EXISTS PYQT5_VERSION) # Already in cache, be silent SET(PYQT5_FOUND TRUE) ELSE(EXISTS PYQT5_VERSION) FIND_FILE(_find_pyqt5_py FindPyQt5.py PATHS ${CMAKE_MODULE_PATH}) - EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} ${_find_pyqt5_py} OUTPUT_VARIABLE pyqt5_config) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_PREFIX_PATH}/share/krita/pykrita" ${PYTHON_EXECUTABLE} ${_find_pyqt5_py} OUTPUT_VARIABLE pyqt5_config) + IF(pyqt5_config) STRING(REGEX REPLACE "^pyqt_version:([^\n]+).*$" "\\1" PYQT5_VERSION ${pyqt5_config}) STRING(REGEX REPLACE ".*\npyqt_version_str:([^\n]+).*$" "\\1" PYQT5_VERSION_STR ${pyqt5_config}) STRING(REGEX REPLACE ".*\npyqt_version_tag:([^\n]+).*$" "\\1" PYQT5_VERSION_TAG ${pyqt5_config}) STRING(REGEX REPLACE ".*\npyqt_sip_dir:([^\n]+).*$" "\\1" PYQT5_SIP_DIR ${pyqt5_config}) STRING(REGEX REPLACE ".*\npyqt_sip_flags:([^\n]+).*$" "\\1" PYQT5_SIP_FLAGS ${pyqt5_config}) SET(PYQT5_FOUND TRUE) ENDIF(pyqt5_config) IF(PYQT5_FOUND) IF(NOT PYQT5_FIND_QUIETLY) MESSAGE(STATUS "Found PyQt5 version: ${PYQT5_VERSION_STR}") ENDIF(NOT PYQT5_FIND_QUIETLY) ELSE(PYQT5_FOUND) IF(PYQT5_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find PyQt5.") ENDIF(PYQT5_FIND_REQUIRED) ENDIF(PYQT5_FOUND) ENDIF(EXISTS PYQT5_VERSION) diff --git a/cmake/modules/FindPyQt5.py b/cmake/modules/FindPyQt5.py index 318b9a3033..6e00a0f0e3 100644 --- a/cmake/modules/FindPyQt5.py +++ b/cmake/modules/FindPyQt5.py @@ -1,28 +1,28 @@ # Copyright (c) 2014, Simon Edwards # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. import PyQt5.Qt import sys import os.path print("pyqt_version:%06.0x" % PyQt5.Qt.PYQT_VERSION) print("pyqt_version_str:%s" % PyQt5.Qt.PYQT_VERSION_STR) pyqt_version_tag = "" in_t = False for item in PyQt5.Qt.PYQT_CONFIGURATION["sip_flags"].split(' '): - if item=="-t": + if item == "-t": in_t = True elif in_t: if item.startswith("Qt_5"): pyqt_version_tag = item else: in_t = False print("pyqt_version_tag:%s" % pyqt_version_tag) # FIXME This next line is just a little bit too crude. pyqt_sip_dir = os.path.join(sys.prefix, "share", "sip", "PyQt5") print("pyqt_sip_dir:%s" % pyqt_sip_dir) print("pyqt_sip_flags:%s" % PyQt5.Qt.PYQT_CONFIGURATION["sip_flags"]) diff --git a/cmake/modules/FindSIP.cmake b/cmake/modules/FindSIP.cmake index 0cbc853ae7..4bb8b40973 100644 --- a/cmake/modules/FindSIP.cmake +++ b/cmake/modules/FindSIP.cmake @@ -1,68 +1,68 @@ # Find SIP # ~~~~~~~~ # # SIP website: http://www.riverbankcomputing.co.uk/sip/index.php # # Find the installed version of SIP. FindSIP should be called after Python # has been found. # # This file defines the following variables: # # SIP_VERSION - The version of SIP found expressed as a 6 digit hex number # suitable for comparison as a string. # # SIP_VERSION_STR - The version of SIP found as a human readable string. # # SIP_EXECUTABLE - Path and filename of the SIP command line executable. # # SIP_INCLUDE_DIR - Directory holding the SIP C++ header file. # # SIP_DEFAULT_SIP_DIR - Default directory where .sip files should be installed # into. # Copyright (c) 2007, Simon Edwards # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. IF(SIP_VERSION) # Already in cache, be silent SET(SIP_FOUND TRUE) ELSE(SIP_VERSION) FIND_FILE(_find_sip_py FindSIP.py PATHS ${CMAKE_MODULE_PATH}) - EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} ${_find_sip_py} OUTPUT_VARIABLE sip_config) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_PREFIX_PATH}/share/krita/pykrita" ${PYTHON_EXECUTABLE} ${_find_sip_py} OUTPUT_VARIABLE sip_config) IF(sip_config) STRING(REGEX REPLACE "^sip_version:([^\n]+).*$" "\\1" SIP_VERSION ${sip_config}) STRING(REGEX REPLACE ".*\nsip_version_str:([^\n]+).*$" "\\1" SIP_VERSION_STR ${sip_config}) STRING(REGEX REPLACE ".*\nsip_bin:([^\n]+).*$" "\\1" SIP_EXECUTABLE ${sip_config}) IF(NOT SIP_DEFAULT_SIP_DIR) STRING(REGEX REPLACE ".*\ndefault_sip_dir:([^\n]+).*$" "\\1" SIP_DEFAULT_SIP_DIR ${sip_config}) ENDIF(NOT SIP_DEFAULT_SIP_DIR) STRING(REGEX REPLACE ".*\nsip_inc_dir:([^\n]+).*$" "\\1" SIP_INCLUDE_DIR ${sip_config}) FILE(TO_CMAKE_PATH ${SIP_DEFAULT_SIP_DIR} SIP_DEFAULT_SIP_DIR) FILE(TO_CMAKE_PATH ${SIP_INCLUDE_DIR} SIP_INCLUDE_DIR) if (WIN32) set(SIP_EXECUTABLE ${SIP_EXECUTABLE}.exe) endif() IF(EXISTS ${SIP_EXECUTABLE}) SET(SIP_FOUND TRUE) ELSE() MESSAGE(STATUS "Found SIP configuration but the sip executable could not be found.") ENDIF() ENDIF(sip_config) IF(SIP_FOUND) IF(NOT SIP_FIND_QUIETLY) MESSAGE(STATUS "Found SIP version: ${SIP_VERSION_STR}") ENDIF(NOT SIP_FIND_QUIETLY) ELSE(SIP_FOUND) IF(SIP_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find SIP") ENDIF(SIP_FIND_REQUIRED) ENDIF(SIP_FOUND) ENDIF(SIP_VERSION) diff --git a/cmake/modules/PythonCompile.py b/cmake/modules/PythonCompile.py index 156fea2884..f93cd8ec0b 100644 --- a/cmake/modules/PythonCompile.py +++ b/cmake/modules/PythonCompile.py @@ -1,4 +1,5 @@ # By Simon Edwards # This file is in the public domain. -import py_compile, sys +import py_compile +import sys sys.exit(py_compile.main()) diff --git a/cmake/modules/SIPMacros.cmake b/cmake/modules/SIPMacros.cmake index 29cc394484..895cddc5bb 100644 --- a/cmake/modules/SIPMacros.cmake +++ b/cmake/modules/SIPMacros.cmake @@ -1,128 +1,132 @@ # Macros for SIP # ~~~~~~~~~~~~~~ # Copyright (c) 2007, Simon Edwards # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # # SIP website: http://www.riverbankcomputing.co.uk/sip/index.php # # This file defines the following macros: # # ADD_SIP_PYTHON_MODULE (MODULE_NAME MODULE_SIP [library1, libaray2, ...]) # Specifies a SIP file to be built into a Python module and installed. # MODULE_NAME is the name of Python module including any path name. (e.g. # os.sys, Foo.bar etc). MODULE_SIP the path and filename of the .sip file # to process and compile. libraryN are libraries that the Python module, # which is typically a shared library, should be linked to. The built # module will also be install into Python's site-packages directory. # # The behaviour of the ADD_SIP_PYTHON_MODULE macro can be controlled by a # number of variables: # # SIP_INCLUDES - List of directories which SIP will scan through when looking # for included .sip files. (Corresponds to the -I option for SIP.) # # SIP_TAGS - List of tags to define when running SIP. (Corresponds to the -t # option for SIP.) # # SIP_CONCAT_PARTS - An integer which defines the number of parts the C++ code # of each module should be split into. Defaults to 8. (Corresponds to the # -j option for SIP.) # # SIP_DISABLE_FEATURES - List of feature names which should be disabled # running SIP. (Corresponds to the -x option for SIP.) # # SIP_EXTRA_OPTIONS - Extra command line options which should be passed on to # SIP. SET(SIP_INCLUDES) SET(SIP_TAGS) SET(SIP_CONCAT_PARTS 8) SET(SIP_DISABLE_FEATURES) SET(SIP_EXTRA_OPTIONS) MACRO(ADD_SIP_PYTHON_MODULE MODULE_NAME MODULE_SIP) SET(EXTRA_LINK_LIBRARIES ${ARGN}) STRING(REPLACE "." "/" _x ${MODULE_NAME}) GET_FILENAME_COMPONENT(_parent_module_path ${_x} PATH) GET_FILENAME_COMPONENT(_child_module_name ${_x} NAME) GET_FILENAME_COMPONENT(_module_path ${MODULE_SIP} PATH) if(_module_path STREQUAL "") set(CMAKE_CURRENT_SIP_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") else(_module_path STREQUAL "") set(CMAKE_CURRENT_SIP_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/${_module_path}") endif(_module_path STREQUAL "") GET_FILENAME_COMPONENT(_abs_module_sip ${MODULE_SIP} ABSOLUTE) # We give this target a long logical target name. # (This is to avoid having the library name clash with any already # install library names. If that happens then cmake dependancy # tracking get confused.) STRING(REPLACE "." "_" _logical_name ${MODULE_NAME}) SET(_logical_name "python_module_${_logical_name}") FILE(MAKE_DIRECTORY ${CMAKE_CURRENT_SIP_OUTPUT_DIR}) # Output goes in this dir. SET(_sip_includes) FOREACH (_inc ${SIP_INCLUDES}) GET_FILENAME_COMPONENT(_abs_inc ${_inc} ABSOLUTE) LIST(APPEND _sip_includes -I ${_abs_inc}) ENDFOREACH (_inc ) SET(_sip_tags) FOREACH (_tag ${SIP_TAGS}) LIST(APPEND _sip_tags -t ${_tag}) ENDFOREACH (_tag) SET(_sip_x) FOREACH (_x ${SIP_DISABLE_FEATURES}) LIST(APPEND _sip_x -x ${_x}) ENDFOREACH (_x ${SIP_DISABLE_FEATURES}) SET(_message "-DMESSAGE=Generating CPP code for module ${MODULE_NAME}") SET(_sip_output_files) FOREACH(CONCAT_NUM RANGE 0 ${SIP_CONCAT_PARTS} ) IF( ${CONCAT_NUM} LESS ${SIP_CONCAT_PARTS} ) SET(_sip_output_files ${_sip_output_files} ${CMAKE_CURRENT_SIP_OUTPUT_DIR}/sip${_child_module_name}part${CONCAT_NUM}.cpp ) ENDIF( ${CONCAT_NUM} LESS ${SIP_CONCAT_PARTS} ) ENDFOREACH(CONCAT_NUM RANGE 0 ${SIP_CONCAT_PARTS} ) IF(NOT WIN32) SET(TOUCH_COMMAND touch) ELSE(NOT WIN32) SET(TOUCH_COMMAND echo) # instead of a touch command, give out the name and append to the files # this is basically what the touch command does. FOREACH(filename ${_sip_output_files}) FILE(APPEND filename "") ENDFOREACH(filename ${_sip_output_files}) ENDIF(NOT WIN32) ADD_CUSTOM_COMMAND( OUTPUT ${_sip_output_files} COMMAND ${CMAKE_COMMAND} -E echo ${message} COMMAND ${TOUCH_COMMAND} ${_sip_output_files} COMMAND ${SIP_EXECUTABLE} ${_sip_tags} ${_sip_x} ${SIP_EXTRA_OPTIONS} -j ${SIP_CONCAT_PARTS} -c ${CMAKE_CURRENT_SIP_OUTPUT_DIR} ${_sip_includes} ${_abs_module_sip} DEPENDS ${_abs_module_sip} ${SIP_EXTRA_FILES_DEPEND} ) # not sure if type MODULE could be uses anywhere, limit to cygwin for now IF (CYGWIN) ADD_LIBRARY(${_logical_name} MODULE ${_sip_output_files} ) ELSE (CYGWIN) ADD_LIBRARY(${_logical_name} SHARED ${_sip_output_files} ) ENDIF (CYGWIN) TARGET_LINK_LIBRARIES(${_logical_name} ${PYTHON_LIBRARY}) TARGET_LINK_LIBRARIES(${_logical_name} ${EXTRA_LINK_LIBRARIES}) SET_TARGET_PROPERTIES(${_logical_name} PROPERTIES PREFIX "" OUTPUT_NAME ${_child_module_name}) + IF (MINGW) + TARGET_COMPILE_DEFINITIONS(${_logical_name} PRIVATE _hypot=hypot) + ENDIF (MINGW) + IF (WIN32) SET_TARGET_PROPERTIES(${_logical_name} PROPERTIES SUFFIX ".pyd") ENDIF (WIN32) INSTALL(TARGETS ${_logical_name} DESTINATION "${PYTHON_SITE_PACKAGES_INSTALL_DIR}/${_parent_module_path}") ENDMACRO(ADD_SIP_PYTHON_MODULE) diff --git a/krita/data/paintoppresets/Oils_bristle.kpp b/krita/data/paintoppresets/Oils_bristle.kpp index 6b31991f64..2fec28a8f1 100644 Binary files a/krita/data/paintoppresets/Oils_bristle.kpp and b/krita/data/paintoppresets/Oils_bristle.kpp differ diff --git a/krita/data/paintoppresets/Oils_knife.kpp b/krita/data/paintoppresets/Oils_knife.kpp index 30214c3f59..80de0c5af9 100644 Binary files a/krita/data/paintoppresets/Oils_knife.kpp and b/krita/data/paintoppresets/Oils_knife.kpp differ diff --git a/krita/data/patterns/26_brush-marks.png b/krita/data/patterns/26_brush-marks.png new file mode 100644 index 0000000000..c963331673 Binary files /dev/null and b/krita/data/patterns/26_brush-marks.png differ diff --git a/krita/data/patterns/CMakeLists.txt b/krita/data/patterns/CMakeLists.txt index b4bd108b63..965d94ea60 100644 --- a/krita/data/patterns/CMakeLists.txt +++ b/krita/data/patterns/CMakeLists.txt @@ -1,85 +1,86 @@ ########### install files ############### install(FILES 01_canvas.png 02_rough-canvas.png 02b_WoofTissue.png 03_default-paper.png 04_paper-C-grain.png 05_paper-torchon.png 06_hard-grain.png 07_big-grain.png 08_bump-relief.png 09_drawed_crosshatched.png 09b_drawed-CrossedLines.png 10_drawed_dotted.png 11_drawed_furry.png 12_drawed_vertical.png 13_drawed_swirl.png 14_texture-rock.png 15_texture-rockB.png 16_texture-woody.png 17_texture-melt.png 18_texture-bark.png 18b_WaveFlex.png 19_texture-crackle.png 20_texture-vegetal.png 21_texture-chainmail.png 22_texture-reptile.png 23-dynamic-screentone-A.png 24-dynamic-screentone-B.png 25-dynamic-screentone-C.png +26_brush-marks.png Canvas_Hard.pat Canvas_covered.pat Canvas_small_01.pat Canvas_small_02.pat Cross01.pat Cross02.pat Cross03.pat Cross04.pat Cross05.pat Cross06.pat Cross07.pat Crumpled_Paper.pat Grid01.pat Grid02.pat Grid03.pat Grid04.pat Grid05.pat HR_PastelPaper_02.pat HR_Wall_Paper.pat Pattern01.pat Pattern02.pat Pattern03.pat Pattern04.pat Pattern05.pat Pattern06.pat Squares01.pat Squares02.pat Squares03.pat Squares04.pat Squares05.pat Squares06.pat Squares07.pat Squares08.pat Squares09.pat Squares10.pat Stripes02.pat Stripes03.pat Stripes04.pat Stripes05.pat Stripes06.pat Stripes07.pat Stripes08.pat Stripes09.pat Zigzag01.pat Zigzag02.pat Zigzag03.pat Zigzag04.pat fractal_pattern.pat generic_paper1.pat generic_paper2.pat granite.pat hexacolBW__2.pat DESTINATION ${DATA_INSTALL_DIR}/krita/patterns) diff --git a/krita/data/templates/animation/.directory b/krita/data/templates/animation/.directory index a462f96be0..41c05f1fe8 100644 --- a/krita/data/templates/animation/.directory +++ b/krita/data/templates/animation/.directory @@ -1,18 +1,19 @@ [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[gl]=Modelos 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 84fb9a835f..dcc2784fc4 100644 --- a/krita/data/templates/animation/Anim-Jp-EN.desktop +++ b/krita/data/templates/animation/Anim-Jp-EN.desktop @@ -1,24 +1,25 @@ [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[gl]=Animación-xaponesa-en-inglés 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 ecfc3e629d..0ac9ccdc76 100644 --- a/krita/data/templates/animation/Anim-Jp-JP.desktop +++ b/krita/data/templates/animation/Anim-Jp-JP.desktop @@ -1,24 +1,25 @@ [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[gl]=Animación-xaponesa-en-xaponés 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/krita.action b/krita/krita.action index 90b5abdf15..758f5363d4 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3006 +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 + &Toggle layer lock - Lock/unlock layer - Lock/unlock layer + Toggle layer lock + Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked - Lock/unlock layer &alpha + Toggle layer &alpha - Lock/unlock layer's alpha - Lock/unlock layer's alpha + Toggle layer alpha + Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 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/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index a3c4977cf9..3d7f8fae12 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,168 +1,171 @@ org.kde.krita.desktop CC0-1.0 Krita Krita Krita Krita Krita Krita Krita Krita + Krita Krita Krita Krita Krita + Krita + Krita Krita Krita xxKritaxx Krita 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 + 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/libs/brush/kis_brush_registry.cpp b/libs/brush/kis_brush_registry.cpp index ddb8ff4550..f64cb5d1d9 100644 --- a/libs/brush/kis_brush_registry.cpp +++ b/libs/brush/kis_brush_registry.cpp @@ -1,77 +1,76 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_registry.h" #include #include #include #include #include #include "kis_brush_server.h" #include "kis_auto_brush_factory.h" #include "kis_text_brush_factory.h" #include "kis_predefined_brush_factory.h" Q_GLOBAL_STATIC(KisBrushRegistry, s_instance) KisBrushRegistry::KisBrushRegistry() { KisBrushServer::instance(); } KisBrushRegistry::~KisBrushRegistry() { Q_FOREACH (const QString & id, keys()) { delete get(id); } dbgRegistry << "deleting KisBrushRegistry"; } KisBrushRegistry* KisBrushRegistry::instance() { if (!s_instance.exists()) { s_instance->add(new KisAutoBrushFactory()); s_instance->add(new KisPredefinedBrushFactory("gbr_brush")); s_instance->add(new KisPredefinedBrushFactory("abr_brush")); s_instance->add(new KisTextBrushFactory()); s_instance->add(new KisPredefinedBrushFactory("png_brush")); s_instance->add(new KisPredefinedBrushFactory("svg_brush")); KoPluginLoader::instance()->load("Krita/Brush", "Type == 'Service' and ([X-Krita-Version] == 28)"); } return s_instance; } KisBrushSP KisBrushRegistry::getOrCreateBrush(const QDomElement& element, bool forceCopy) { QString brushType = element.attribute("type"); if (brushType.isEmpty()) return 0; KisBrushFactory* factory = get(brushType); if (!factory) return 0; - KisBrushSP brush = factory->getOrCreateBrush(element, forceCopy); - return brush; + return factory->getOrCreateBrush(element, forceCopy); } diff --git a/libs/brush/kis_text_brush_factory.cpp b/libs/brush/kis_text_brush_factory.cpp index 51b4a8b79d..6ae8055071 100644 --- a/libs/brush/kis_text_brush_factory.cpp +++ b/libs/brush/kis_text_brush_factory.cpp @@ -1,45 +1,48 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_text_brush_factory.h" #include #include #include #include "kis_text_brush.h" KisBrushSP KisTextBrushFactory::getOrCreateBrush(const QDomElement& brushDefinition, bool forceCopy) { Q_UNUSED(forceCopy); QString text = brushDefinition.attribute("text", "The quick brown fox ate your text"); QFont font; font.fromString(brushDefinition.attribute("font")); double spacing = KisDomUtils::toDouble(brushDefinition.attribute("spacing", "1.0")); QString pipeMode = brushDefinition.attribute("pipe", "false"); bool pipe = (pipeMode == "true") ? true : false; - KisTextBrush *brush = new KisTextBrush(); + KisBrushSP b = new KisTextBrush(); + + KisTextBrush *brush = dynamic_cast(b.data()); + brush->setText(text); brush->setFont(font); brush->setPipeMode(pipe); brush->setSpacing(spacing); brush->updateBrush(); - return brush; + return b; } diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index 14a6f8584a..d28dcbd6e6 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2431 +1,2432 @@ /* This file is part of the KDE project Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2006-2010 Thorsten Zachmann Copyright (C) 2007-2009,2011 Jan Hambrecht CopyRight (C) 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 "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeLayer.h" #include "KoShapeContainerModel.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "KoInsets.h" #include "KoShapeStrokeModel.h" #include "KoShapeBackground.h" #include "KoColorBackground.h" #include "KoHatchBackground.h" #include "KoGradientBackground.h" #include "KoPatternBackground.h" #include "KoShapeManager.h" #include "KoShapeUserData.h" #include "KoShapeApplicationData.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoViewConverter.h" #include "KoShapeStroke.h" #include "KoShapeShadow.h" #include "KoClipPath.h" #include "KoPathShape.h" #include "KoOdfWorkaround.h" #include "KoFilterEffectStack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include #include "KoOdfGradientBackground.h" #include // KoShapePrivate KoShapePrivate::KoShapePrivate(KoShape *shape) : q_ptr(shape), size(50, 50), parent(0), shadow(0), border(0), filterEffectStack(0), transparency(0.0), zIndex(0), runThrough(0), visible(true), printable(true), geometryProtected(false), keepAspect(false), selectable(true), detectCollision(false), protectContent(false), textRunAroundSide(KoShape::BiggestRunAroundSide), textRunAroundDistanceLeft(0.0), textRunAroundDistanceTop(0.0), textRunAroundDistanceRight(0.0), textRunAroundDistanceBottom(0.0), textRunAroundThreshold(0.0), textRunAroundContour(KoShape::ContourFull) { connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint); connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint); connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint); connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint); connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } KoShapePrivate::KoShapePrivate(const KoShapePrivate &rhs, KoShape *q) : q_ptr(q), size(rhs.size), shapeId(rhs.shapeId), name(rhs.name), localMatrix(rhs.localMatrix), connectors(rhs.connectors), parent(0), // to be initialized later shapeManagers(), // to be initialized later toolDelegates(), // FIXME: how to initialize them? userData(rhs.userData ? rhs.userData->clone() : 0), stroke(rhs.stroke), fill(rhs.fill), dependees(), // FIXME: how to initialize them? shadow(0), // WARNING: not implemented in Krita border(0), // WARNING: not implemented in Krita clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0), clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0), additionalAttributes(rhs.additionalAttributes), additionalStyleAttributes(rhs.additionalStyleAttributes), filterEffectStack(0), // WARNING: not implemented in Krita transparency(rhs.transparency), hyperLink(rhs.hyperLink), zIndex(rhs.zIndex), runThrough(rhs.runThrough), visible(rhs.visible), printable(rhs.visible), geometryProtected(rhs.geometryProtected), keepAspect(rhs.keepAspect), selectable(rhs.selectable), detectCollision(rhs.detectCollision), protectContent(rhs.protectContent), textRunAroundSide(rhs.textRunAroundSide), textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft), textRunAroundDistanceTop(rhs.textRunAroundDistanceTop), textRunAroundDistanceRight(rhs.textRunAroundDistanceRight), textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom), textRunAroundThreshold(rhs.textRunAroundThreshold), textRunAroundContour(rhs.textRunAroundContour) { } KoShapePrivate::~KoShapePrivate() { Q_Q(KoShape); if (parent) { parent->removeShape(q); } Q_FOREACH (KoShapeManager *manager, shapeManagers) { manager->shapeInterface()->notifyShapeDestructed(q); } shapeManagers.clear(); if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } void KoShapePrivate::shapeChanged(KoShape::ChangeType type) { Q_Q(KoShape); if (parent) parent->model()->childChanged(q, type); q->shapeChanged(type); Q_FOREACH (KoShape * shape, dependees) { shape->shapeChanged(type, q); } Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) { listener->notifyShapeChangedImpl(type, q); } } void KoShapePrivate::addShapeManager(KoShapeManager *manager) { shapeManagers.insert(manager); } void KoShapePrivate::removeShapeManager(KoShapeManager *manager) { shapeManagers.remove(manager); } void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toRelative(point.position, shapeSize); point.position.rx() = qBound(0.0, point.position.x(), 1.0); point.position.ry() = qBound(0.0, point.position.y(), 1.0); break; case KoConnectionPoint::AlignRight: point.position.rx() -= shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() -= shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() -= shapeSize.width(); point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() -= 0.5 * shapeSize.width(); point.position.ry() -= 0.5 * shapeSize.height(); break; } } void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toAbsolute(point.position, shapeSize); break; case KoConnectionPoint::AlignRight: point.position.rx() += shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() += shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() += shapeSize.width(); point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() += 0.5 * shapeSize.width(); point.position.ry() += 0.5 * shapeSize.height(); break; } } // static QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString value; if (styleStack.hasProperty(KoXmlNS::draw, property)) { value = styleStack.property(KoXmlNS::draw, property); } return value; } // ======== KoShape KoShape::KoShape() : d_ptr(new KoShapePrivate(this)) { notifyChanged(); } KoShape::KoShape(KoShapePrivate *dd) : d_ptr(dd) { } KoShape::~KoShape() { Q_D(KoShape); d->shapeChanged(Deleted); delete d_ptr; } KoShape *KoShape::cloneShape() const { + KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "not implemented!"); return 0; } void KoShape::scale(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * scaleMatrix; notifyChanged(); d->shapeChanged(ScaleChanged); } void KoShape::rotate(qreal angle) { Q_D(KoShape); QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height())); QTransform rotateMatrix; rotateMatrix.translate(center.x(), center.y()); rotateMatrix.rotate(angle); rotateMatrix.translate(-center.x(), -center.y()); d->localMatrix = d->localMatrix * rotateMatrix; notifyChanged(); d->shapeChanged(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * shearMatrix; notifyChanged(); d->shapeChanged(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { Q_D(KoShape); QSizeF oldSize(size()); // always set size, as d->size and size() may vary d->size = newSize; if (oldSize == newSize) return; notifyChanged(); d->shapeChanged(SizeChanged); } void KoShape::setPosition(const QPointF &newPosition) { Q_D(KoShape); QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); d->localMatrix = d->localMatrix * translateMatrix; notifyChanged(); d->shapeChanged(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { Q_D(const KoShape); if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); QRectF bb(QPointF(), size()); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (bb.contains(point)) return true; // if there is no shadow we can as well just leave if (! d->shadow) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - d->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { Q_D(const KoShape); QTransform transform = absoluteTransformation(0); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (d->shadow) { KoInsets insets; d->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (d->filterEffectStack) { QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect()); bb |= transform.mapRect(clipRect); } return bb; } QRectF KoShape::boundingRect(const QList &shapes) { QRectF boundingRect; Q_FOREACH (KoShape *shape, shapes) { boundingRect |= shape->boundingRect(); } return boundingRect; } QRectF KoShape::absoluteOutlineRect(KoViewConverter *converter) const { return absoluteTransformation(converter).map(outline()).boundingRect(); } QRectF KoShape::absoluteOutlineRect(const QList &shapes, KoViewConverter *converter) { QRectF absoluteOutlineRect; Q_FOREACH (KoShape *shape, shapes) { absoluteOutlineRect |= shape->absoluteOutlineRect(converter); } return absoluteOutlineRect; } QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const { Q_D(const KoShape); QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { // We do need to pass the converter here, otherwise the parent's // translation is not inherited. matrix = container->absoluteTransformation(converter); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); if (converter) containerPos = converter->documentToView(containerPos); matrix.translate(containerPos.x(), containerPos.y()); } } if (converter) { QPointF pos = d->localMatrix.map(QPointF()); QPointF trans = converter->documentToView(pos) - pos; matrix.translate(trans.x(), trans.y()); } return d->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(0); // the transformation is relative to the global coordinate system // but we want to change the local matrix, so convert the matrix // to be relative to the local coordinate system QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted(); applyTransformation(transformMatrix); } void KoShape::applyTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix * d->localMatrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } QTransform KoShape::transformation() const { Q_D(const KoShape); return d->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { /** * WARNING: Our definition of zIndex is not yet compatible with SVG2's * definition. In SVG stacking context of groups with the same * zIndex are **merged**, while in Krita the contents of groups * is never merged. One group will always below than the other. * Therefore, when zIndex of two groups inside the same parent * coinside, the resulting painting order in Krita is * **UNDEFINED**. * * To avoid this trouble we use KoShapeReorderCommand::mergeInShape() * inside KoShapeCreateCommand. */ // First sort according to runThrough which is sort of a master level KoShape *parentShapeS1 = s1->parent(); KoShape *parentShapeS2 = s2->parent(); int runThrough1 = s1->runThrough(); int runThrough2 = s2->runThrough(); while (parentShapeS1) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough1 = parentShapeS1->runThrough(); } else { runThrough1 = runThrough1 + parentShapeS1->runThrough(); } parentShapeS1 = parentShapeS1->parent(); } while (parentShapeS2) { if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough2 = parentShapeS2->runThrough(); } else { runThrough2 = runThrough2 + parentShapeS2->runThrough(); } parentShapeS2 = parentShapeS2->parent(); } if (runThrough1 > runThrough2) { return false; } if (runThrough1 < runThrough2) { return true; } // If on the same runThrough level then the zIndex is all that matters. // // We basically walk up through the parents until we find a common base parent // To do that we need two loops where the inner loop walks up through the parents // of s2 every time we step up one parent level on s1 // // We don't update the index value until after we have seen that it's not a common base // That way we ensure that two children of a common base are sorted according to their respective // z value bool foundCommonParent = false; int index1 = s1->zIndex(); int index2 = s2->zIndex(); parentShapeS1 = s1; parentShapeS2 = s2; while (parentShapeS1 && !foundCommonParent) { parentShapeS2 = s2; index2 = parentShapeS2->zIndex(); while (parentShapeS2) { if (parentShapeS2 == parentShapeS1) { foundCommonParent = true; break; } if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { index2 = parentShapeS2->zIndex(); } parentShapeS2 = parentShapeS2->parent(); } if (!foundCommonParent) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { index1 = parentShapeS1->zIndex(); } parentShapeS1 = parentShapeS1->parent(); } } // If the one shape is a parent/child of the other then sort so. if (s1 == parentShapeS2) { return true; } if (s2 == parentShapeS1) { return false; } // If we went that far then the z-Index is used for sorting. return index1 < index2; } void KoShape::setParent(KoShapeContainer *parent) { Q_D(KoShape); if (d->parent == parent) { return; } KoShapeContainer *oldParent = d->parent; d->parent = 0; // avoids recursive removing if (oldParent) { oldParent->shapeInterface()->removeShape(this); } KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this); if (parent && parent != this) { d->parent = parent; parent->shapeInterface()->addShape(this); } notifyChanged(); d->shapeChanged(ParentChanged); } bool KoShape::inheritsTransformFromAny(const QList ancestorsInQuestion) const { bool result = false; KoShape *shape = const_cast(this); while (shape) { KoShapeContainer *parent = shape->parent(); if (parent && !parent->inheritsTransform(shape)) { break; } if (ancestorsInQuestion.contains(shape)) { result = true; break; } shape = parent; } return result; } int KoShape::zIndex() const { Q_D(const KoShape); return d->zIndex; } void KoShape::update() const { Q_D(const KoShape); if (!d->shapeManagers.empty()) { QRectF rect(boundingRect()); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::updateAbsolute(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } Q_D(const KoShape); if (!d->shapeManagers.empty() && isVisible()) { Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect); } } } QPainterPath KoShape::outline() const { QPainterPath path; path.addRect(outlineRect()); return path; } QRectF KoShape::outlineRect() const { const QSizeF s = size(); return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)), qMax(s.height(), qreal(0.0001)))); } QPainterPath KoShape::shadowOutline() const { Q_D(const KoShape); if (d->fill) { return outline(); } return QPainterPath(); } QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const { const QRectF rc = outlineRect(); QPointF point = rc.topLeft(); bool valid = false; QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid); if (valid) { point = anchoredPoint; } return absoluteTransformation(0).map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor) { Q_D(KoShape); QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); d->shapeChanged(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { Q_D(KoShape); d->size = shape->size(); d->connectors.clear(); Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints()) addConnectionPoint(point); d->zIndex = shape->zIndex(); d->visible = shape->isVisible(); // Ensure printable is true by default if (!d->visible) d->printable = true; else d->printable = shape->isPrintable(); d->geometryProtected = shape->isGeometryProtected(); d->protectContent = shape->isContentProtected(); d->selectable = shape->isSelectable(); d->keepAspect = shape->keepAspectRatio(); d->localMatrix = shape->d_ptr->localMatrix; } void KoShape::notifyChanged() { Q_D(KoShape); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { Q_D(KoShape); d->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { Q_D(const KoShape); return d->userData.data(); } bool KoShape::hasTransparency() const { Q_D(const KoShape); if (! d->fill) return true; else return d->fill->hasTransparency() || d->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { Q_D(KoShape); d->transparency = qBound(0.0, transparency, 1.0); d->shapeChanged(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { Q_D(const KoShape); if (!recursive || !parent()) { return d->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-d->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { Q_D(const KoShape); KoInsets answer; if (d->stroke) d->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { Q_D(const KoShape); // try to extract the rotation angle out of the local matrix // if it is a pure rotation matrix // check if the matrix has shearing mixed in if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { Q_D(const KoShape); return d->size; } QPointF KoShape::position() const { Q_D(const KoShape); QPointF center = outlineRect().center(); return d->localMatrix.map(center) - center; } int KoShape::addConnectionPoint(const KoConnectionPoint &point) { Q_D(KoShape); // get next glue point id int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint; if (d->connectors.size()) nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1); KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[nextConnectionPointId] = p; return nextConnectionPointId; } bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point) { Q_D(KoShape); if (connectionPointId < 0) return false; const bool insertPoint = !hasConnectionPoint(connectionPointId); switch(connectionPointId) { case KoConnectionPoint::TopConnectionPoint: case KoConnectionPoint::RightConnectionPoint: case KoConnectionPoint::BottomConnectionPoint: case KoConnectionPoint::LeftConnectionPoint: { KoConnectionPoint::PointId id = static_cast(connectionPointId); d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id); break; } default: { KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[connectionPointId] = p; break; } } if(!insertPoint) d->shapeChanged(ConnectionPointChanged); return true; } bool KoShape::hasConnectionPoint(int connectionPointId) const { Q_D(const KoShape); return d->connectors.contains(connectionPointId); } KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const { Q_D(const KoShape); KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint()); // convert glue point to shape coordinates d->convertToShapeCoordinates(p, size()); return p; } KoConnectionPoints KoShape::connectionPoints() const { Q_D(const KoShape); QSizeF s = size(); KoConnectionPoints points = d->connectors; KoConnectionPoints::iterator point = points.begin(); KoConnectionPoints::iterator lastPoint = points.end(); // convert glue points to shape coordinates for(; point != lastPoint; ++point) { d->convertToShapeCoordinates(point.value(), s); } return points; } void KoShape::removeConnectionPoint(int connectionPointId) { Q_D(KoShape); d->connectors.remove(connectionPointId); d->shapeChanged(ConnectionPointChanged); } void KoShape::clearConnectionPoints() { Q_D(KoShape); d->connectors.clear(); } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { Q_D(const KoShape); return d->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { Q_D(KoShape); if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( d->textRunAroundSide == side) { return; } d->textRunAroundSide = side; notifyChanged(); d->shapeChanged(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { Q_D(const KoShape); return d->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { Q_D(const KoShape); return d->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { Q_D(const KoShape); return d->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { Q_D(const KoShape); return d->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { Q_D(const KoShape); return d->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { Q_D(KoShape); d->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { Q_D(const KoShape); return d->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { Q_D(KoShape); d->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { Q_D(KoShape); d->fill = fill; d->shapeChanged(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { Q_D(const KoShape); return d->fill; } void KoShape::setZIndex(int zIndex) { Q_D(KoShape); if (d->zIndex == zIndex) return; d->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() { Q_D(const KoShape); return d->runThrough; } void KoShape::setRunThrough(short int runThrough) { Q_D(KoShape); d->runThrough = runThrough; } void KoShape::setVisible(bool on) { Q_D(KoShape); int _on = (on ? 1 : 0); if (d->visible == _on) return; d->visible = _on; } bool KoShape::isVisible(bool recursive) const { Q_D(const KoShape); if (! recursive) return d->visible; if (recursive && ! d->visible) return false; KoShapeContainer * parentShape = parent(); while (parentShape) { if (! parentShape->isVisible()) return false; parentShape = parentShape->parent(); } return true; } void KoShape::setPrintable(bool on) { Q_D(KoShape); d->printable = on; } bool KoShape::isPrintable() const { Q_D(const KoShape); if (d->visible) return d->printable; else return false; } void KoShape::setSelectable(bool selectable) { Q_D(KoShape); d->selectable = selectable; } bool KoShape::isSelectable() const { Q_D(const KoShape); return d->selectable; } void KoShape::setGeometryProtected(bool on) { Q_D(KoShape); d->geometryProtected = on; } bool KoShape::isGeometryProtected() const { Q_D(const KoShape); return d->geometryProtected; } void KoShape::setContentProtected(bool protect) { Q_D(KoShape); d->protectContent = protect; } bool KoShape::isContentProtected() const { Q_D(const KoShape); return d->protectContent; } KoShapeContainer *KoShape::parent() const { Q_D(const KoShape); return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { Q_D(KoShape); d->keepAspect = keepAspect; d->shapeChanged(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { Q_D(const KoShape); return d->keepAspect; } QString KoShape::shapeId() const { Q_D(const KoShape); return d->shapeId; } void KoShape::setShapeId(const QString &id) { Q_D(KoShape); d->shapeId = id; } void KoShape::setCollisionDetection(bool detect) { Q_D(KoShape); d->detectCollision = detect; } bool KoShape::collisionDetection() { Q_D(KoShape); return d->detectCollision; } KoShapeStrokeModelSP KoShape::stroke() const { Q_D(const KoShape); return d->stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { Q_D(KoShape); d->stroke = stroke; d->shapeChanged(StrokeChanged); notifyChanged(); } void KoShape::setShadow(KoShapeShadow *shadow) { Q_D(KoShape); if (d->shadow) d->shadow->deref(); d->shadow = shadow; if (d->shadow) { d->shadow->ref(); // TODO update changed area } d->shapeChanged(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { Q_D(const KoShape); return d->shadow; } void KoShape::setBorder(KoBorder *border) { Q_D(KoShape); if (d->border) { // The shape owns the border. delete d->border; } d->border = border; d->shapeChanged(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { Q_D(const KoShape); return d->border; } void KoShape::setClipPath(KoClipPath *clipPath) { Q_D(KoShape); d->clipPath.reset(clipPath); d->shapeChanged(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { Q_D(const KoShape); return d->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { Q_D(KoShape); d->clipMask.reset(clipMask); } KoClipMask* KoShape::clipMask() const { Q_D(const KoShape); return d->clipMask.data(); } QTransform KoShape::transform() const { Q_D(const KoShape); return d->localMatrix; } QString KoShape::name() const { Q_D(const KoShape); return d->name; } void KoShape::setName(const QString &name) { Q_D(KoShape); d->name = name; } void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } bool KoShape::isEditable() const { Q_D(const KoShape); if (!d->visible || d->geometryProtected) return false; if (d->parent && d->parent->isChildLocked(this)) return false; return true; } // painting void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); KoBorder *bd = border(); if (!bd) { return; } QRectF borderRect = QRectF(QPointF(0, 0), size()); // Paint the border. bd->paint(painter, borderRect, KoBorder::PaintInsideLine); } // loading & saving methods QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoShape); // and fill the style KoShapeStrokeModelSP sm = stroke(); if (sm) { sm->fillStyle(style, context); } else { style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); } KoShapeShadow *s = shadow(); if (s) s->fillStyle(style, context); QSharedPointer bg = background(); if (bg) { bg->fillStyle(style, context); } else { style.addProperty("draw:fill", "none", KoGenStyle::GraphicType); } KoBorder *b = border(); if (b) { b->saveOdf(style); } if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) { style.setAutoStyleInStylesDotXml(true); } QString value; if (isGeometryProtected()) { value = "position size"; } if (isContentProtected()) { if (! value.isEmpty()) value += ' '; value += "content"; } if (!value.isEmpty()) { style.addProperty("style:protect", value, KoGenStyle::GraphicType); } QMap::const_iterator it(d->additionalStyleAttributes.constBegin()); for (; it != d->additionalStyleAttributes.constEnd(); ++it) { style.addProperty(it.key(), it.value()); } if (parent() && parent()->isClipped(this)) { /* * In Calligra clipping is done using a parent shape which can be rotated, sheared etc * and even non-square. So the ODF interoperability version we write here is really * just a very simple version of that... */ qreal top = -position().y(); qreal left = -position().x(); qreal right = parent()->size().width() - size().width() - left; qreal bottom = parent()->size().height() - size().height() - top; style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)") .arg(top, 10, 'f').arg(right, 10, 'f') .arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType); } QString wrap; switch (textRunAroundSide()) { case BiggestRunAroundSide: wrap = "biggest"; break; case LeftRunAroundSide: wrap = "left"; break; case RightRunAroundSide: wrap = "right"; break; case EnoughRunAroundSide: wrap = "dynamic"; break; case BothRunAroundSide: wrap = "parallel"; break; case NoRunAround: wrap = "none"; break; case RunThrough: wrap = "run-through"; break; } style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType); switch (textRunAroundContour()) { case ContourBox: style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType); break; case ContourFull: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType); break; case ContourOutside: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType); break; } style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType); if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight()) && (textRunAroundDistanceTop() == textRunAroundDistanceBottom()) && (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) { style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); } else { style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType); } return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr"); } void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); d->fill.clear(); d->stroke.clear(); if (d->shadow && !d->shadow->deref()) { delete d->shadow; d->shadow = 0; } setBackground(loadOdfFill(context)); setStroke(loadOdfStroke(element, context)); setShadow(d->loadOdfShadow(context)); setBorder(d->loadOdfBorder(context)); QString protect(styleStack.property(KoXmlNS::style, "protect")); setGeometryProtected(protect.contains("position") || protect.contains("size")); setContentProtected(protect.contains("content")); QString margin = styleStack.property(KoXmlNS::fo, "margin"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-left"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-top"); if (!margin.isEmpty()) { setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-right"); if (!margin.isEmpty()) { setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-bottom"); if (!margin.isEmpty()) { setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } QString wrap; if (styleStack.hasProperty(KoXmlNS::style, "wrap")) { wrap = styleStack.property(KoXmlNS::style, "wrap"); } else { // no value given in the file, but guess biggest wrap = "biggest"; } if (wrap == "none") { setTextRunAroundSide(KoShape::NoRunAround); } else if (wrap == "run-through") { QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background"); if (runTrought == "background") { setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); } else { setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); } } else { if (wrap == "biggest") setTextRunAroundSide(KoShape::BiggestRunAroundSide); else if (wrap == "left") setTextRunAroundSide(KoShape::LeftRunAroundSide); else if (wrap == "right") setTextRunAroundSide(KoShape::RightRunAroundSide); else if (wrap == "dynamic") setTextRunAroundSide(KoShape::EnoughRunAroundSide); else if (wrap == "parallel") setTextRunAroundSide(KoShape::BothRunAroundSide); } if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) { QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold"); if (!wrapThreshold.isEmpty()) { setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold)); } } if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") { if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") { setTextRunAroundContour(KoShape::ContourFull); } else { setTextRunAroundContour(KoShape::ContourOutside); } } else { setTextRunAroundContour(KoShape::ContourBox); } } bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes) { if (attributes & OdfPosition) { QPointF pos(position()); if (element.hasAttributeNS(KoXmlNS::svg, "x")) pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "y")) pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos); } if (attributes & OdfSize) { QSizeF s(size()); if (element.hasAttributeNS(KoXmlNS::svg, "width")) s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "height")) s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); setSize(s); } if (attributes & OdfLayer) { if (element.hasAttributeNS(KoXmlNS::draw, "layer")) { KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer")); if (layer) { setParent(layer); } } } if (attributes & OdfId) { KoElementReference ref; ref.loadOdf(element); if (ref.isValid()) { context.addShapeId(this, ref.toString()); } } if (attributes & OdfZIndex) { if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) { setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt()); } else { setZIndex(context.zIndex()); } } if (attributes & OdfName) { if (element.hasAttributeNS(KoXmlNS::draw, "name")) { setName(element.attributeNS(KoXmlNS::draw, "name")); } } if (attributes & OdfStyle) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); } if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation"); } loadStyle(element, context); styleStack.restore(); } if (attributes & OdfTransformation) { QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString()); if (! transform.isEmpty()) applyAbsoluteTransformation(parseOdfTransform(transform)); } if (attributes & OdfAdditionalAttributes) { QSet additionalAttributeData = KoShapeLoadingContext::additionalAttributeData(); Q_FOREACH (const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) { if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) { QString value = element.attributeNS(attributeData.ns, attributeData.tag); //debugFlake << "load additional attribute" << attributeData.tag << value; setAdditionalAttribute(attributeData.name, value); } } } if (attributes & OdfCommonChildElements) { // load glue points (connection points) loadOdfGluePoints(element, context); } return true; } QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const { QString fill = KoShapePrivate::getStyleProperty("fill", context); QSharedPointer bg; if (fill == "solid") { bg = QSharedPointer(new KoColorBackground()); } else if (fill == "hatch") { bg = QSharedPointer(new KoHatchBackground()); } else if (fill == "gradient") { QString styleName = KoShapePrivate::getStyleProperty("fill-gradient-name", context); KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName]; QString style; if (e) { style = e->attributeNS(KoXmlNS::draw, "style", QString()); } if ((style == "rectangular") || (style == "square")) { bg = QSharedPointer(new KoOdfGradientBackground()); } else { QGradient *gradient = new QLinearGradient(); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); bg = QSharedPointer(new KoGradientBackground(gradient)); } } else if (fill == "bitmap") { bg = QSharedPointer(new KoPatternBackground(context.imageCollection())); #ifndef NWORKAROUND_ODF_BUGS } else if (fill.isEmpty()) { bg = QSharedPointer(KoOdfWorkaround::fixBackgroundColor(this, context)); return bg; #endif } else { return QSharedPointer(0); } if (!bg->loadStyle(context.odfLoadingContext(), size())) { return QSharedPointer(0); } return bg; } KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); QString stroke = KoShapePrivate::getStyleProperty("stroke", context); if (stroke == "solid" || stroke == "dash") { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader); QSharedPointer stroke(new KoShapeStroke()); if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) { QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient"); QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size()); stroke->setLineBrush(brush); } else { stroke->setColor(pen.color()); } #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); return stroke; #ifndef NWORKAROUND_ODF_BUGS } else if (stroke.isEmpty()) { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader); if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) { QSharedPointer stroke(new KoShapeStroke()); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); stroke->setColor(pen.color()); return stroke; } #endif } return KoShapeStrokeModelSP(); } KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context); if (shadowStyle == "visible" || shadowStyle == "hidden") { KoShapeShadow *shadow = new KoShapeShadow(); QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color")); qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x")); qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y")); shadow->setOffset(QPointF(offsetX, offsetY)); qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius")); shadow->setBlur(blur); QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0); shadow->setColor(shadowColor); shadow->setVisible(shadowStyle == "visible"); return shadow; } return 0; } KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoBorder *border = new KoBorder(); if (border->loadOdf(styleStack)) { return border; } delete border; return 0; } void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoXmlElement child; bool hasCenterGluePoint = false; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "glue-point") continue; // NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated // all use of draw:id for xml:id, it didn't specify that here, so it // doesn't support xml:id (and so, maybe, shouldn't use KoElementReference. const QString id = child.attributeNS(KoXmlNS::draw, "id", QString()); const int index = id.toInt(); // connection point in center should be default but odf doesn't support, // in new shape, first custom point is in center, it's okay to replace that point // with point from xml now, we'll add it back later if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint || (index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) { warnFlake << "glue-point with no or invalid id"; continue; } QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified(); QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified(); if(xStr.isEmpty() || yStr.isEmpty()) { warnFlake << "glue-point with invald position"; continue; } KoConnectionPoint connector; const QString align = child.attributeNS(KoXmlNS::draw, "align", QString()); if (align.isEmpty()) { #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixGluePointPosition(xStr, context); KoOdfWorkaround::fixGluePointPosition(yStr, context); #endif if(!xStr.endsWith('%') || !yStr.endsWith('%')) { warnFlake << "glue-point with invald position"; continue; } // x and y are relative to drawing object center connector.position.setX(xStr.remove('%').toDouble()/100.0); connector.position.setY(yStr.remove('%').toDouble()/100.0); // convert position to be relative to top-left corner connector.position += QPointF(0.5, 0.5); connector.position.rx() = qBound(0.0, connector.position.x(), 1.0); connector.position.ry() = qBound(0.0, connector.position.y(), 1.0); } else { // absolute distances to the edge specified by align connector.position.setX(KoUnit::parseValue(xStr)); connector.position.setY(KoUnit::parseValue(yStr)); if (align == "top-left") { connector.alignment = KoConnectionPoint::AlignTopLeft; } else if (align == "top") { connector.alignment = KoConnectionPoint::AlignTop; } else if (align == "top-right") { connector.alignment = KoConnectionPoint::AlignTopRight; } else if (align == "left") { connector.alignment = KoConnectionPoint::AlignLeft; } else if (align == "center") { connector.alignment = KoConnectionPoint::AlignCenter; } else if (align == "right") { connector.alignment = KoConnectionPoint::AlignRight; } else if (align == "bottom-left") { connector.alignment = KoConnectionPoint::AlignBottomLeft; } else if (align == "bottom") { connector.alignment = KoConnectionPoint::AlignBottom; } else if (align == "bottom-right") { connector.alignment = KoConnectionPoint::AlignBottomRight; } debugFlake << "using alignment" << align; } const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString()); if (!escape.isEmpty()) { if (escape == "horizontal") { connector.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (escape == "vertical") { connector.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (escape == "left") { connector.escapeDirection = KoConnectionPoint::LeftDirection; } else if (escape == "right") { connector.escapeDirection = KoConnectionPoint::RightDirection; } else if (escape == "up") { connector.escapeDirection = KoConnectionPoint::UpDirection; } else if (escape == "down") { connector.escapeDirection = KoConnectionPoint::DownDirection; } debugFlake << "using escape direction" << escape; } d->connectors[index] = connector; debugFlake << "loaded glue-point" << index << "at position" << connector.position; if (d->connectors[index].position == QPointF(0.5, 0.5)) { hasCenterGluePoint = true; debugFlake << "center glue-point found at id " << index; } } if (!hasCenterGluePoint) { d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } debugFlake << "shape has now" << d->connectors.count() << "glue-points"; } void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor) { Q_D(KoShape); KoXmlElement child; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "contour-polygon") continue; debugFlake << "shape loads contour-polygon"; KoPathShape *ps = new KoPathShape(); ps->loadContourOdf(child, context, scaleFactor); ps->setTransformation(transformation()); KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse); d->clipPath.reset(clipPath); } } QTransform KoShape::parseOdfTransform(const QString &transform) { QTransform matrix; // Split string for handling 1 transform statement at a time QStringList subtransforms = transform.split(')', QString::SkipEmptyParts); QStringList::ConstIterator it = subtransforms.constBegin(); QStringList::ConstIterator end = subtransforms.constEnd(); for (; it != end; ++it) { QStringList subtransform = (*it).split('(', QString::SkipEmptyParts); subtransform[0] = subtransform[0].trimmed().toLower(); subtransform[1] = subtransform[1].simplified(); QRegExp reg("[,( ]"); QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts); if (subtransform[0].startsWith(';') || subtransform[0].startsWith(',')) subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); QString cmd = subtransform[0].toLower(); if (cmd == "rotate") { QTransform rotMatrix; if (params.count() == 3) { qreal x = KoUnit::parseValue(params[1]); qreal y = KoUnit::parseValue(params[2]); rotMatrix.translate(x, y); // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); rotMatrix.translate(-x, -y); } else { // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); } matrix = matrix * rotMatrix; } else if (cmd == "translate") { QTransform moveMatrix; if (params.count() == 2) { qreal x = KoUnit::parseValue(params[0]); qreal y = KoUnit::parseValue(params[1]); moveMatrix.translate(x, y); } else // Spec : if only one param given, assume 2nd param to be 0 moveMatrix.translate(KoUnit::parseValue(params[0]) , 0); matrix = matrix * moveMatrix; } else if (cmd == "scale") { QTransform scaleMatrix; if (params.count() == 2) scaleMatrix.scale(params[0].toDouble(), params[1].toDouble()); else // Spec : if only one param given, assume uniform scaling scaleMatrix.scale(params[0].toDouble(), params[0].toDouble()); matrix = matrix * scaleMatrix; } else if (cmd == "skewx") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(tan(-params[0].toDouble()), 0.0F); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "skewy") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(0.0F, tan(-params[0].toDouble())); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "matrix") { QTransform m; if (params.count() >= 6) { m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0, params[2].toDouble(), params[3].toDouble(), 0, KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1); } matrix = matrix * m; } } return matrix; } void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const { Q_D(const KoShape); if (attributes & OdfStyle) { KoGenStyle style; // all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape if (context.isSet(KoShapeSavingContext::PresentationShape)) { style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation"); context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context)); } else { style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic"); context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context)); } } if (attributes & OdfId) { if (context.isSet(KoShapeSavingContext::DrawId)) { KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter); ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId); } } if (attributes & OdfName) { if (! name().isEmpty()) context.xmlWriter().addAttribute("draw:name", name()); } if (attributes & OdfLayer) { KoShape *parent = d->parent; while (parent) { if (dynamic_cast(parent)) { context.xmlWriter().addAttribute("draw:layer", parent->name()); break; } parent = parent->parent(); } } if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) { context.xmlWriter().addAttribute("draw:z-index", zIndex()); } if (attributes & OdfSize) { QSizeF s(size()); if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size // clipping in ODF is done using a combination of visual size and content cliprect. // A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out // of proportion in this case). If we then add a fo:clip like; // fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left) // our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box. // TODO do this properly by subtracting rects s = parent()->size(); } context.xmlWriter().addAttributePt("svg:width", s.width()); context.xmlWriter().addAttributePt("svg:height", s.height()); } // The position is implicitly stored in the transformation matrix // if the transformation is saved as well if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) { const QPointF p(position() * context.shapeOffset(this)); context.xmlWriter().addAttributePt("svg:x", p.x()); context.xmlWriter().addAttributePt("svg:y", p.y()); } if (attributes & OdfTransformation) { QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this); if (! matrix.isIdentity()) { if (qAbs(matrix.m11() - 1) < 1E-5 // 1 && qAbs(matrix.m12()) < 1E-5 // 0 && qAbs(matrix.m21()) < 1E-5 // 0 && qAbs(matrix.m22() - 1) < 1E-5) { // 1 context.xmlWriter().addAttributePt("svg:x", matrix.dx()); context.xmlWriter().addAttributePt("svg:y", matrix.dy()); } else { QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)") .arg(matrix.m11(), 0, 'f', 11) .arg(matrix.m12(), 0, 'f', 11) .arg(matrix.m21(), 0, 'f', 11) .arg(matrix.m22(), 0, 'f', 11) .arg(matrix.dx(), 0, 'f', 11) .arg(matrix.dy(), 0, 'f', 11); context.xmlWriter().addAttribute("draw:transform", m); } } } if (attributes & OdfViewbox) { const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); } if (attributes & OdfAdditionalAttributes) { QMap::const_iterator it(d->additionalAttributes.constBegin()); for (; it != d->additionalAttributes.constEnd(); ++it) { context.xmlWriter().addAttribute(it.key().toUtf8(), it.value()); } } } void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const { Q_D(const KoShape); // save glue points see ODF 9.2.19 Glue Points if(d->connectors.count()) { KoConnectionPoints::const_iterator cp = d->connectors.constBegin(); KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd(); for(; cp != lastCp; ++cp) { // do not save default glue points if(cp.key() < 4) continue; context.xmlWriter().startElement("draw:glue-point"); context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key())); if (cp.value().alignment == KoConnectionPoint::AlignNone) { // convert to percent from center const qreal x = cp.value().position.x() * 100.0 - 50.0; const qreal y = cp.value().position.y() * 100.0 - 50.0; context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x)); context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y)); } else { context.xmlWriter().addAttributePt("svg:x", cp.value().position.x()); context.xmlWriter().addAttributePt("svg:y", cp.value().position.y()); } QString escapeDirection; switch(cp.value().escapeDirection) { case KoConnectionPoint::HorizontalDirections: escapeDirection = "horizontal"; break; case KoConnectionPoint::VerticalDirections: escapeDirection = "vertical"; break; case KoConnectionPoint::LeftDirection: escapeDirection = "left"; break; case KoConnectionPoint::RightDirection: escapeDirection = "right"; break; case KoConnectionPoint::UpDirection: escapeDirection = "up"; break; case KoConnectionPoint::DownDirection: escapeDirection = "down"; break; default: // fall through break; } if(!escapeDirection.isEmpty()) { context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection); } QString alignment; switch(cp.value().alignment) { case KoConnectionPoint::AlignTopLeft: alignment = "top-left"; break; case KoConnectionPoint::AlignTop: alignment = "top"; break; case KoConnectionPoint::AlignTopRight: alignment = "top-right"; break; case KoConnectionPoint::AlignLeft: alignment = "left"; break; case KoConnectionPoint::AlignCenter: alignment = "center"; break; case KoConnectionPoint::AlignRight: alignment = "right"; break; case KoConnectionPoint::AlignBottomLeft: alignment = "bottom-left"; break; case KoConnectionPoint::AlignBottom: alignment = "bottom"; break; case KoConnectionPoint::AlignBottomRight: alignment = "bottom-right"; break; default: // fall through break; } if(!alignment.isEmpty()) { context.xmlWriter().addAttribute("draw:align", alignment); } context.xmlWriter().endElement(); } } } void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const { Q_D(const KoShape); debugFlake << "shape saves contour-polygon"; if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) { // This will loose data as odf can only save one set of contour wheras // svg loading and at least karbon editing can produce more than one // TODO, FIXME see if we can save more than one clipshape to odf d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize); } } // end loading & saving methods // static void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter) { qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.scale(zoomX, zoomY); } KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform()); KoShape::applyConversion(*painter, converter); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation(0).map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation(0).mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation(0).inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation(0).inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { Q_D(KoShape); if (! shape) return false; // refuse to establish a circular dependency if (shape->hasDependee(this)) return false; if (! d->dependees.contains(shape)) d->dependees.append(shape); return true; } void KoShape::removeDependee(KoShape *shape) { Q_D(KoShape); int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { Q_D(const KoShape); return d->dependees.contains(shape); } QList KoShape::dependees() const { Q_D(const KoShape); return d->dependees; } void KoShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } KoSnapData KoShape::snapData() const { return KoSnapData(); } void KoShape::setAdditionalAttribute(const QString &name, const QString &value) { Q_D(KoShape); d->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { Q_D(KoShape); d->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { Q_D(KoShape); d->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { Q_D(KoShape); d->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { Q_D(const KoShape); return d->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { Q_D(KoShape); if (d->filterEffectStack) d->filterEffectStack->deref(); d->filterEffectStack = filterEffectStack; if (d->filterEffectStack) { d->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { Q_D(const KoShape); return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { Q_D(KoShape); d->toolDelegates = delegates; } QString KoShape::hyperLink () const { Q_D(const KoShape); return d->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { Q_D(KoShape); d->hyperLink = hyperLink; } KoShapePrivate *KoShape::priv() { Q_D(KoShape); return d; } KoShape::ShapeChangeListener::~ShapeChangeListener() { Q_FOREACH(KoShape *shape, m_registeredShapes) { shape->removeShapeChangeListener(this); } } void KoShape::ShapeChangeListener::registerShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape)); m_registeredShapes.append(shape); } void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); m_registeredShapes.removeAll(shape); } void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); notifyShapeChanged(type, shape); if (type == KoShape::Deleted) { unregisterShape(shape); } } void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener)); d->listeners.removeAll(listener); listener->unregisterShape(this); } QList KoShape::linearizeSubtree(const QList &shapes) { QList result; Q_FOREACH (KoShape *shape, shapes) { result << shape; KoShapeContainer *container = dynamic_cast(shape); if (container) { result << linearizeSubtree(container->shapes()); } } return result; } diff --git a/libs/image/commands/kis_node_property_list_command.cpp b/libs/image/commands/kis_node_property_list_command.cpp index 801fcdf2ad..d794f224de 100644 --- a/libs/image/commands/kis_node_property_list_command.cpp +++ b/libs/image/commands/kis_node_property_list_command.cpp @@ -1,147 +1,163 @@ /* * Copyright (c) 2009 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kis_node.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_selection_mask.h" #include "kis_paint_layer.h" #include "commands/kis_node_property_list_command.h" #include "kis_undo_adapter.h" #include "kis_layer_properties_icons.h" +// HACK! please refactor out! +#include "kis_simple_stroke_strategy.h" + KisNodePropertyListCommand::KisNodePropertyListCommand(KisNodeSP node, KisBaseNode::PropertyList newPropertyList) : KisNodeCommand(kundo2_i18n("Property Changes"), node), m_newPropertyList(newPropertyList), m_oldPropertyList(node->sectionModelProperties()) /** * TODO instead of "Property Changes" check which property * has been changed and display either lock/unlock, visible/hidden * or "Property Changes" (this require new strings) */ { } void KisNodePropertyListCommand::redo() { m_node->setSectionModelProperties(m_newPropertyList); doUpdate(m_oldPropertyList, m_newPropertyList); } void KisNodePropertyListCommand::undo() { m_node->setSectionModelProperties(m_oldPropertyList); doUpdate(m_newPropertyList, m_oldPropertyList); } bool checkOnionSkinChanged(const KisBaseNode::PropertyList &oldPropertyList, const KisBaseNode::PropertyList &newPropertyList) { if (oldPropertyList.size() != newPropertyList.size()) return false; bool oldOnionSkinsValue = false; bool newOnionSkinsValue = false; Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) { oldOnionSkinsValue = prop.state.toBool(); } } Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) { newOnionSkinsValue = prop.state.toBool(); } } return oldOnionSkinsValue != newOnionSkinsValue; } void KisNodePropertyListCommand::doUpdate(const KisBaseNode::PropertyList &oldPropertyList, const KisBaseNode::PropertyList &newPropertyList) { bool oldPassThroughValue = false; bool newPassThroughValue = false; Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { if (prop.name == i18n("Pass Through")) { oldPassThroughValue = prop.state.toBool(); } } Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { if (prop.name == i18n("Pass Through")) { newPassThroughValue = prop.state.toBool(); } } if (oldPassThroughValue && !newPassThroughValue) { KisLayerSP layer(qobject_cast(m_node.data())); KisImageSP image = layer->image().toStrongRef(); if (image) { image->refreshGraphAsync(layer); } } else if (m_node->parent() && !oldPassThroughValue && newPassThroughValue) { KisLayerSP layer(qobject_cast(m_node->parent().data())); KisImageSP image = layer->image().toStrongRef(); if (image) { image->refreshGraphAsync(layer); } } else if (checkOnionSkinChanged(oldPropertyList, newPropertyList)) { m_node->setDirtyDontResetAnimationCache(); } else { m_node->setDirty(); // TODO check if visibility was changed or not } } void KisNodePropertyListCommand::setNodePropertiesNoUndo(KisNodeSP node, KisImageSP image, PropertyList proplist) { bool undo = true; Q_FOREACH (const KisBaseNode::Property &prop, proplist) { if (prop.isInStasis) undo = false; if (prop.name == i18n("Visible") && node->visible() != prop.state.toBool()) undo = false; if (prop.name == i18n("Locked") && node->userLocked() != prop.state.toBool()) undo = false; if (prop.name == i18n("Active")) { if (KisSelectionMask *m = dynamic_cast(node.data())) { if (m->active() != prop.state.toBool()) { undo = false; } } } if (prop.name == i18n("Alpha Locked")) { if (KisPaintLayer* l = dynamic_cast(node.data())) { if (l->alphaLocked() != prop.state.toBool()) { undo = false; } } } } QScopedPointer cmd(new KisNodePropertyListCommand(node, proplist)); if (undo) { image->undoAdapter()->addCommand(cmd.take()); } else { image->setModified(); cmd->redo(); + + /** + * HACK ALERT! + * + * Here we start a fake legacy stroke, so that all the LoD planes would + * be invalidated. Ideally, we should refactor this method and avoid + * resetting LoD planes when node visibility changes, Instead there should + * be two commands executes: LoD agnostic one (which sets the properties + * themselves), and two LoD-specific update commands: one for lodN and + * another one for lod0. + */ + KisStrokeId strokeId = image->startStroke(new KisSimpleStrokeStrategy()); + image->endStroke(strokeId); } } diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp index d6cb78d09c..a08aada3a3 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,420 +1,424 @@ /* * 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)) { + if (rhs.m_d->opacityChannel) { + m_d->opacityChannel.reset(new KisScalarKeyframeChannel(*rhs.m_d->opacityChannel, 0)); + m_d->keyframeChannels.insert(m_d->opacityChannel->id(), m_d->opacityChannel.data()); + } } 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; } QMap KisBaseNode::keyframeChannels() const { 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_composite_progress_proxy.cpp b/libs/image/kis_composite_progress_proxy.cpp index 257566d45f..81fed1da22 100644 --- a/libs/image/kis_composite_progress_proxy.cpp +++ b/libs/image/kis_composite_progress_proxy.cpp @@ -1,66 +1,73 @@ /* * 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_composite_progress_proxy.h" #include "kis_debug.h" void KisCompositeProgressProxy::addProxy(KoProgressProxy *proxy) { m_proxies.append(proxy); if (!m_uniqueProxies.contains(proxy)) { m_uniqueProxies.append(proxy); } } void KisCompositeProgressProxy::removeProxy(KoProgressProxy *proxy) { m_proxies.removeOne(proxy); if (!m_proxies.contains(proxy)) { m_uniqueProxies.removeOne(proxy); } } int KisCompositeProgressProxy::maximum() const { if(m_proxies.isEmpty()) return 0; return m_proxies.first()->maximum(); } void KisCompositeProgressProxy::setValue(int value) { Q_FOREACH (KoProgressProxy *proxy, m_uniqueProxies) { proxy->setValue(value); } } void KisCompositeProgressProxy::setRange(int minimum, int maximum) { Q_FOREACH (KoProgressProxy *proxy, m_uniqueProxies) { proxy->setRange(minimum, maximum); } } void KisCompositeProgressProxy::setFormat(const QString &format) { Q_FOREACH (KoProgressProxy *proxy, m_uniqueProxies) { proxy->setFormat(format); } } +void KisCompositeProgressProxy::setAutoNestedName(const QString &name) +{ + Q_FOREACH (KoProgressProxy *proxy, m_uniqueProxies) { + proxy->setAutoNestedName(name); + } +} + diff --git a/libs/image/kis_composite_progress_proxy.h b/libs/image/kis_composite_progress_proxy.h index 82ad6339c6..efcd9dacc5 100644 --- a/libs/image/kis_composite_progress_proxy.h +++ b/libs/image/kis_composite_progress_proxy.h @@ -1,43 +1,44 @@ /* * 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_COMPOSITE_PROGRESS_PROXY_H #define __KIS_COMPOSITE_PROGRESS_PROXY_H #include #include #include "kritaimage_export.h" class KRITAIMAGE_EXPORT KisCompositeProgressProxy : public KoProgressProxy { public: void addProxy(KoProgressProxy *proxy); void removeProxy(KoProgressProxy *proxy); int maximum() const override; void setValue(int value) override; void setRange(int minimum, int maximum) override; void setFormat(const QString &format) override; + void setAutoNestedName(const QString &name) override; private: QList m_proxies; QList m_uniqueProxies; }; #endif /* __KIS_COMPOSITE_PROGRESS_PROXY_H */ diff --git a/libs/image/kis_keyframe_channel.cpp b/libs/image/kis_keyframe_channel.cpp index 36b8e97a99..776ef195a7 100644 --- a/libs/image/kis_keyframe_channel.cpp +++ b/libs/image/kis_keyframe_channel.cpp @@ -1,540 +1,540 @@ /* * 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_keyframe_channel.h" #include "KoID.h" #include "kis_global.h" #include "kis_node.h" #include "kis_time_range.h" #include "kundo2command.h" #include "kis_keyframe_commands.h" #include const KoID KisKeyframeChannel::Content = KoID("content", ki18n("Content")); const KoID KisKeyframeChannel::Opacity = KoID("opacity", ki18n("Opacity")); const KoID KisKeyframeChannel::TransformArguments = KoID("transform_arguments", ki18n("Transform")); const KoID KisKeyframeChannel::TransformPositionX = KoID("transform_pos_x", ki18n("Position (X)")); const KoID KisKeyframeChannel::TransformPositionY = KoID("transform_pos_y", ki18n("Position (Y)")); const KoID KisKeyframeChannel::TransformScaleX = KoID("transform_scale_x", ki18n("Scale (X)")); const KoID KisKeyframeChannel::TransformScaleY = KoID("transform_scale_y", ki18n("Scale (Y)")); const KoID KisKeyframeChannel::TransformShearX = KoID("transform_shear_x", ki18n("Shear (X)")); const KoID KisKeyframeChannel::TransformShearY = KoID("transform_shear_y", ki18n("Shear (Y)")); const KoID KisKeyframeChannel::TransformRotationX = KoID("transform_rotation_x", ki18n("Rotation (X)")); const KoID KisKeyframeChannel::TransformRotationY = KoID("transform_rotation_y", ki18n("Rotation (Y)")); const KoID KisKeyframeChannel::TransformRotationZ = KoID("transform_rotation_z", ki18n("Rotation (Z)")); struct KisKeyframeChannel::Private { Private() {} Private(const Private &rhs, KisNodeWSP newParentNode) { node = newParentNode; id = rhs.id; defaultBounds = rhs.defaultBounds; } KeyframesMap keys; KisNodeWSP node; KoID id; KisDefaultBoundsBaseSP defaultBounds; }; KisKeyframeChannel::KisKeyframeChannel(const KoID &id, KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private) { m_d->id = id; m_d->node = 0; m_d->defaultBounds = defaultBounds; } -KisKeyframeChannel::KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNodeWSP newParentNode) +KisKeyframeChannel::KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNode *newParentNode) : m_d(new Private(*rhs.m_d, newParentNode)) { KIS_ASSERT_RECOVER_NOOP(&rhs != this); Q_FOREACH(KisKeyframeSP keyframe, rhs.m_d->keys) { m_d->keys.insert(keyframe->time(), keyframe->cloneFor(this)); } } KisKeyframeChannel::~KisKeyframeChannel() {} QString KisKeyframeChannel::id() const { return m_d->id.id(); } QString KisKeyframeChannel::name() const { return m_d->id.name(); } void KisKeyframeChannel::setNode(KisNodeWSP node) { m_d->node = node; } KisNodeWSP KisKeyframeChannel::node() const { return m_d->node; } int KisKeyframeChannel::keyframeCount() const { return m_d->keys.count(); } KisKeyframeChannel::KeyframesMap& KisKeyframeChannel::keys() { return m_d->keys; } const KisKeyframeChannel::KeyframesMap& KisKeyframeChannel::constKeys() const { return m_d->keys; } #define LAZY_INITIALIZE_PARENT_COMMAND(cmd) \ QScopedPointer __tempCommand; \ if (!parentCommand) { \ __tempCommand.reset(new KUndo2Command()); \ cmd = __tempCommand.data(); \ } KisKeyframeSP KisKeyframeChannel::addKeyframe(int time, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); return insertKeyframe(time, KisKeyframeSP(), parentCommand); } KisKeyframeSP KisKeyframeChannel::copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); return insertKeyframe(newTime, keyframe, parentCommand); } KisKeyframeSP KisKeyframeChannel::insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) { KisKeyframeSP keyframe = keyframeAt(time); if (keyframe) { deleteKeyframeImpl(keyframe, parentCommand, false); } Q_ASSERT(parentCommand); keyframe = createKeyframe(time, copySrc, parentCommand); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), keyframe, parentCommand); cmd->redo(); return keyframe; } bool KisKeyframeChannel::deleteKeyframe(KisKeyframeSP keyframe, KUndo2Command *parentCommand) { return deleteKeyframeImpl(keyframe, parentCommand, true); } bool KisKeyframeChannel::moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); if (newTime == keyframe->time()) return false; KisKeyframeSP other = keyframeAt(newTime); if (other) { deleteKeyframeImpl(other, parentCommand, false); } const int srcTime = keyframe->time(); KUndo2Command *cmd = new KisMoveFrameCommand(this, keyframe, srcTime, newTime, parentCommand); cmd->redo(); if (srcTime == 0) { addKeyframe(srcTime, parentCommand); } return true; } bool KisKeyframeChannel::deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); Q_ASSERT(parentCommand); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), KisKeyframeSP(), parentCommand); cmd->redo(); destroyKeyframe(keyframe, parentCommand); if (recreate && keyframe->time() == 0) { addKeyframe(0, parentCommand); } return true; } void KisKeyframeChannel::moveKeyframeImpl(KisKeyframeSP keyframe, int newTime) { KIS_ASSERT_RECOVER_RETURN(keyframe); KIS_ASSERT_RECOVER_RETURN(!keyframeAt(newTime)); KisTimeRange rangeSrc = affectedFrames(keyframe->time()); QRect rectSrc = affectedRect(keyframe); emit sigKeyframeAboutToBeMoved(keyframe, newTime); m_d->keys.remove(keyframe->time()); int oldTime = keyframe->time(); keyframe->setTime(newTime); m_d->keys.insert(newTime, keyframe); emit sigKeyframeMoved(keyframe, oldTime); KisTimeRange rangeDst = affectedFrames(keyframe->time()); QRect rectDst = affectedRect(keyframe); requestUpdate(rangeSrc, rectSrc); requestUpdate(rangeDst, rectDst); } KisKeyframeSP KisKeyframeChannel::replaceKeyframeAt(int time, KisKeyframeSP newKeyframe) { Q_ASSERT(newKeyframe.isNull() || time == newKeyframe->time()); KisKeyframeSP existingKeyframe = keyframeAt(time); if (!existingKeyframe.isNull()) { removeKeyframeLogical(existingKeyframe); } if (!newKeyframe.isNull()) { insertKeyframeLogical(newKeyframe); } return existingKeyframe; } void KisKeyframeChannel::insertKeyframeLogical(KisKeyframeSP keyframe) { const int time = keyframe->time(); emit sigKeyframeAboutToBeAdded(keyframe); m_d->keys.insert(time, keyframe); emit sigKeyframeAdded(keyframe); QRect rect = affectedRect(keyframe); KisTimeRange range = affectedFrames(time); requestUpdate(range, rect); } void KisKeyframeChannel::removeKeyframeLogical(KisKeyframeSP keyframe) { QRect rect = affectedRect(keyframe); KisTimeRange range = affectedFrames(keyframe->time()); emit sigKeyframeAboutToBeRemoved(keyframe); m_d->keys.remove(keyframe->time()); emit sigKeyframeRemoved(keyframe); requestUpdate(range, rect); } KisKeyframeSP KisKeyframeChannel::keyframeAt(int time) const { KeyframesMap::const_iterator i = m_d->keys.constFind(time); if (i != m_d->keys.constEnd()) { return i.value(); } return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::activeKeyframeAt(int time) const { KeyframesMap::const_iterator i = activeKeyIterator(time); if (i != m_d->keys.constEnd()) { return i.value(); } return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::currentlyActiveKeyframe() const { return activeKeyframeAt(currentTime()); } KisKeyframeSP KisKeyframeChannel::firstKeyframe() const { if (m_d->keys.isEmpty()) return KisKeyframeSP(); return m_d->keys.first(); } KisKeyframeSP KisKeyframeChannel::nextKeyframe(KisKeyframeSP keyframe) const { KeyframesMap::const_iterator i = m_d->keys.constFind(keyframe->time()); if (i == m_d->keys.constEnd()) return KisKeyframeSP(0); i++; if (i == m_d->keys.constEnd()) return KisKeyframeSP(0); return i.value(); } KisKeyframeSP KisKeyframeChannel::previousKeyframe(KisKeyframeSP keyframe) const { KeyframesMap::const_iterator i = m_d->keys.constFind(keyframe->time()); if (i == m_d->keys.constBegin() || i == m_d->keys.constEnd()) return KisKeyframeSP(0); i--; return i.value(); } KisKeyframeSP KisKeyframeChannel::lastKeyframe() const { if (m_d->keys.isEmpty()) return KisKeyframeSP(0); return (m_d->keys.end()-1).value(); } int KisKeyframeChannel::framesHash() const { KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); int hash = 0; while (it != end) { hash += it.key(); ++it; } return hash; } QSet KisKeyframeChannel::allKeyframeIds() const { QSet frames; KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); while (it != end) { frames.insert(it.key()); ++it; } return frames; } KisTimeRange KisKeyframeChannel::affectedFrames(int time) const { if (m_d->keys.isEmpty()) return KisTimeRange::infinite(0); KeyframesMap::const_iterator active = activeKeyIterator(time); KeyframesMap::const_iterator next; int from; if (active == m_d->keys.constEnd()) { // No active keyframe, ie. time is before the first keyframe from = 0; next = m_d->keys.constBegin(); } else { from = active.key(); next = active + 1; } if (next == m_d->keys.constEnd()) { return KisTimeRange::infinite(from); } else { return KisTimeRange::fromTime(from, next.key() - 1); } } KisTimeRange KisKeyframeChannel::identicalFrames(int time) const { KeyframesMap::const_iterator active = activeKeyIterator(time); if (active != m_d->keys.constEnd() && (active+1) != m_d->keys.constEnd()) { if (active->data()->interpolationMode() != KisKeyframe::Constant) { return KisTimeRange::fromTime(time, time); } } return affectedFrames(time); } int KisKeyframeChannel::keyframeRowIndexOf(KisKeyframeSP keyframe) const { KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); int row = 0; for (; it != end; ++it) { if (it.value().data() == keyframe) { return row; } row++; } return -1; } KisKeyframeSP KisKeyframeChannel::keyframeAtRow(int row) const { KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); for (; it != end; ++it) { if (row <= 0) { return it.value(); } row--; } return KisKeyframeSP(); } int KisKeyframeChannel::keyframeInsertionRow(int time) const { KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); int row = 0; for (; it != end; ++it) { if (it.value()->time() > time) { break; } row++; } return row; } QDomElement KisKeyframeChannel::toXML(QDomDocument doc, const QString &layerFilename) { QDomElement channelElement = doc.createElement("channel"); channelElement.setAttribute("name", id()); Q_FOREACH (KisKeyframeSP keyframe, m_d->keys.values()) { QDomElement keyframeElement = doc.createElement("keyframe"); keyframeElement.setAttribute("time", keyframe->time()); keyframeElement.setAttribute("color-label", keyframe->colorLabel()); saveKeyframe(keyframe, keyframeElement, layerFilename); channelElement.appendChild(keyframeElement); } return channelElement; } void KisKeyframeChannel::loadXML(const QDomElement &channelNode) { for (QDomElement keyframeNode = channelNode.firstChildElement(); !keyframeNode.isNull(); keyframeNode = keyframeNode.nextSiblingElement()) { if (keyframeNode.nodeName().toUpper() != "KEYFRAME") continue; KisKeyframeSP keyframe = loadKeyframe(keyframeNode); if (keyframeNode.hasAttribute("color-label")) { keyframe->setColorLabel(keyframeNode.attribute("color-label").toUInt()); } m_d->keys.insert(keyframe->time(), keyframe); } } KisKeyframeSP KisKeyframeChannel::copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand) { if (srcChannel->id() != id()) { warnKrita << "Cannot copy frames from different ids:" << ppVar(srcChannel->id()) << ppVar(id()); return KisKeyframeSP(); } LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); KisKeyframeSP dstFrame = keyframeAt(dstTime); if (dstFrame) { deleteKeyframeImpl(dstFrame, parentCommand, false); } KisKeyframeSP newKeyframe = createKeyframe(dstTime, KisKeyframeSP(), parentCommand); uploadExternalKeyframe(srcChannel, srcTime, newKeyframe); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, newKeyframe->time(), newKeyframe, parentCommand); cmd->redo(); return newKeyframe; } KisKeyframeChannel::KeyframesMap::const_iterator KisKeyframeChannel::activeKeyIterator(int time) const { KeyframesMap::const_iterator i = const_cast(&m_d->keys)->upperBound(time); if (i == m_d->keys.constBegin()) return m_d->keys.constEnd(); return --i; } void KisKeyframeChannel::requestUpdate(const KisTimeRange &range, const QRect &rect) { if (m_d->node) { m_d->node->invalidateFrames(range, rect); int currentTime = m_d->defaultBounds->currentTime(); if (range.contains(currentTime)) { m_d->node->setDirty(rect); } } } int KisKeyframeChannel::currentTime() const { return m_d->defaultBounds->currentTime(); } qreal KisKeyframeChannel::minScalarValue() const { return 0; } qreal KisKeyframeChannel::maxScalarValue() const { return 0; } qreal KisKeyframeChannel::scalarValue(const KisKeyframeSP keyframe) const { Q_UNUSED(keyframe); return 0; } void KisKeyframeChannel::setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand) { Q_UNUSED(keyframe); Q_UNUSED(value); Q_UNUSED(parentCommand); } diff --git a/libs/image/kis_keyframe_channel.h b/libs/image/kis_keyframe_channel.h index 94f8ff9b21..294fde4095 100644 --- a/libs/image/kis_keyframe_channel.h +++ b/libs/image/kis_keyframe_channel.h @@ -1,165 +1,165 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_KEYFRAME_CHANNEL_H #define KIS_KEYFRAME_CHANNEL_H #include #include #include #include "kis_types.h" #include "KoID.h" #include "kritaimage_export.h" #include "kis_keyframe.h" #include "kis_default_bounds.h" class KisTimeRange; class KRITAIMAGE_EXPORT KisKeyframeChannel : public QObject { Q_OBJECT public: // Standard Keyframe Ids static const KoID Content; static const KoID Opacity; static const KoID TransformArguments; static const KoID TransformPositionX; static const KoID TransformPositionY; static const KoID TransformScaleX; static const KoID TransformScaleY; static const KoID TransformShearX; static const KoID TransformShearY; static const KoID TransformRotationX; static const KoID TransformRotationY; static const KoID TransformRotationZ; public: KisKeyframeChannel(const KoID& id, KisDefaultBoundsBaseSP defaultBounds); - KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNodeWSP newParentNode); + KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNode *newParentNode); ~KisKeyframeChannel() override; QString id() const; QString name() const; void setNode(KisNodeWSP node); KisNodeWSP node() const; KisKeyframeSP addKeyframe(int time, KUndo2Command *parentCommand = 0); bool deleteKeyframe(KisKeyframeSP keyframe, KUndo2Command *parentCommand = 0); bool moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0); KisKeyframeSP copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0); KisKeyframeSP copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0); KisKeyframeSP keyframeAt(int time) const; KisKeyframeSP activeKeyframeAt(int time) const; KisKeyframeSP currentlyActiveKeyframe() const; KisKeyframeSP firstKeyframe() const; KisKeyframeSP nextKeyframe(KisKeyframeSP keyframe) const; KisKeyframeSP previousKeyframe(KisKeyframeSP keyframe) const; KisKeyframeSP lastKeyframe() const; /** * Calculates a pseudo-unique keyframes hash. The hash changes * every time any frame is added/removed/moved */ int framesHash() const; QSet allKeyframeIds() const; /** * Get the set of frames affected by any changes to the value * of the active keyframe at the given time. */ KisTimeRange affectedFrames(int time) const; /** * Get a set of frames for which the channel gives identical * results, compared to the given frame. * * Note: this set may be different than the set of affected frames * due to interpolation. */ KisTimeRange identicalFrames(int time) const; int keyframeCount() const; int keyframeRowIndexOf(KisKeyframeSP keyframe) const; KisKeyframeSP keyframeAtRow(int row) const; int keyframeInsertionRow(int time) const; virtual bool hasScalarValue() const = 0; virtual qreal minScalarValue() const; virtual qreal maxScalarValue() const; virtual qreal scalarValue(const KisKeyframeSP keyframe) const; virtual void setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand = 0); virtual QDomElement toXML(QDomDocument doc, const QString &layerFilename); virtual void loadXML(const QDomElement &channelNode); int currentTime() const; Q_SIGNALS: void sigKeyframeAboutToBeAdded(KisKeyframeSP keyframe); void sigKeyframeAdded(KisKeyframeSP keyframe); void sigKeyframeAboutToBeRemoved(KisKeyframeSP keyframe); void sigKeyframeRemoved(KisKeyframeSP keyframe); void sigKeyframeAboutToBeMoved(KisKeyframeSP keyframe, int toTime); void sigKeyframeMoved(KisKeyframeSP keyframe, int fromTime); void sigKeyframeChanged(KisKeyframeSP keyframe); protected: typedef QMap KeyframesMap; KeyframesMap &keys(); const KeyframesMap &constKeys() const; KeyframesMap::const_iterator activeKeyIterator(int time) const; virtual KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) = 0; virtual void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) = 0; virtual void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) = 0; virtual QRect affectedRect(KisKeyframeSP key) = 0; virtual void requestUpdate(const KisTimeRange &range, const QRect &rect); virtual KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) = 0; virtual void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) = 0; private: KisKeyframeSP replaceKeyframeAt(int time, KisKeyframeSP newKeyframe); void insertKeyframeLogical(KisKeyframeSP keyframe); void removeKeyframeLogical(KisKeyframeSP keyframe); bool deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate); void moveKeyframeImpl(KisKeyframeSP keyframe, int newTime); friend class KisMoveFrameCommand; friend class KisReplaceKeyframeCommand; private: KisKeyframeSP insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand); struct Private; QScopedPointer m_d; }; #endif // KIS_KEYFRAME_CHANNEL_H diff --git a/libs/image/kis_name_server.cpp b/libs/image/kis_name_server.cpp index 4487daa05c..1df964ee21 100644 --- a/libs/image/kis_name_server.cpp +++ b/libs/image/kis_name_server.cpp @@ -1,44 +1,49 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_name_server.h" KisNameServer::KisNameServer(qint32 seed) { m_generator = seed; } +KisNameServer::KisNameServer(const KisNameServer &rhs) + : m_generator(rhs.m_generator) +{ +} + KisNameServer::~KisNameServer() { } qint32 KisNameServer::currentSeed() const { return m_generator; } qint32 KisNameServer::number() { return m_generator++; } void KisNameServer::rollback() { m_generator--; } diff --git a/libs/image/kis_name_server.h b/libs/image/kis_name_server.h index aed1e92d63..24d10d7932 100644 --- a/libs/image/kis_name_server.h +++ b/libs/image/kis_name_server.h @@ -1,39 +1,40 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_NAMESERVER_H_ #define KIS_NAMESERVER_H_ #include #include class KRITAIMAGE_EXPORT KisNameServer { public: KisNameServer(qint32 seed = 1); + KisNameServer(const KisNameServer &rhs); ~KisNameServer(); qint32 number(); qint32 currentSeed() const; void rollback(); private: qint32 m_generator; }; #endif // KIS_NAMESERVER_H_ diff --git a/libs/image/kis_node.cpp b/libs/image/kis_node.cpp index 5fedb82dd9..01c22f01a4 100644 --- a/libs/image/kis_node.cpp +++ b/libs/image/kis_node.cpp @@ -1,636 +1,643 @@ /* * 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.h" #include #include #include #include #include #include #include #include #include "kis_global.h" #include "kis_node_graph_listener.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_node_progress_proxy.h" #include "kis_busy_progress_indicator.h" #include "kis_clone_layer.h" #include "kis_safe_read_list.h" typedef KisSafeReadList KisSafeReadNodeList; #include "kis_abstract_projection_plane.h" #include "kis_projection_leaf.h" #include "kis_undo_adapter.h" #include "kis_keyframe_channel.h" /** *The link between KisProjection ans KisImageUpdater *uses queued signals with an argument of KisNodeSP type, *so we should register it beforehand */ struct KisNodeSPStaticRegistrar { KisNodeSPStaticRegistrar() { qRegisterMetaType("KisNodeSP"); } }; static KisNodeSPStaticRegistrar __registrar1; struct KisNodeListStaticRegistrar { KisNodeListStaticRegistrar() { qRegisterMetaType("KisNodeList"); } }; static KisNodeListStaticRegistrar __registrar2; /** * Note about "thread safety" of KisNode * * 1) One can *read* any information about node and node graph in any * number of threads concurrently. This operation is safe because * of the usage of KisSafeReadNodeList and will run concurrently * (lock-free). * * 2) One can *write* any information into the node or node graph in a * single thread only! Changing the graph concurrently is *not* * sane and therefore not supported. * * 3) One can *read and write* information about the node graph * concurrently, given that there is only *one* writer thread and * any number of reader threads. Please note that in this case the * node's code is just guaranteed *not to crash*, which is ensured * by nodeSubgraphLock. You need to ensure the sanity of the data * read by the reader threads yourself! */ struct Q_DECL_HIDDEN KisNode::Private { public: Private(KisNode *node) : graphListener(0) , nodeProgressProxy(0) , busyProgressIndicator(0) , projectionLeaf(new KisProjectionLeaf(node)) { } KisNodeWSP parent; KisNodeGraphListener *graphListener; KisSafeReadNodeList nodes; KisNodeProgressProxy *nodeProgressProxy; KisBusyProgressIndicator *busyProgressIndicator; QReadWriteLock nodeSubgraphLock; KisProjectionLeafSP projectionLeaf; const KisNode* findSymmetricClone(const KisNode *srcRoot, const KisNode *dstRoot, const KisNode *srcTarget); void processDuplicatedClones(const KisNode *srcDuplicationRoot, const KisNode *dstDuplicationRoot, KisNode *node); }; /** * Finds the layer in \p dstRoot subtree, which has the same path as * \p srcTarget has in \p srcRoot */ const KisNode* KisNode::Private::findSymmetricClone(const KisNode *srcRoot, const KisNode *dstRoot, const KisNode *srcTarget) { if (srcRoot == srcTarget) return dstRoot; KisSafeReadNodeList::const_iterator srcIter = srcRoot->m_d->nodes.constBegin(); KisSafeReadNodeList::const_iterator dstIter = dstRoot->m_d->nodes.constBegin(); for (; srcIter != srcRoot->m_d->nodes.constEnd(); srcIter++, dstIter++) { KIS_ASSERT_RECOVER_RETURN_VALUE((srcIter != srcRoot->m_d->nodes.constEnd()) == (dstIter != dstRoot->m_d->nodes.constEnd()), 0); const KisNode *node = findSymmetricClone(srcIter->data(), dstIter->data(), srcTarget); if (node) return node; } return 0; } /** * This function walks through a subtrees of old and new layers and * searches for clone layers. For each clone layer it checks whether * its copyFrom() lays inside the old subtree, and if it is so resets * it to the corresponding layer in the new subtree. * * That is needed when the user duplicates a group layer with all its * layer subtree. In such a case all the "internal" clones must stay * "internal" and not point to the layers of the older group. */ void KisNode::Private::processDuplicatedClones(const KisNode *srcDuplicationRoot, const KisNode *dstDuplicationRoot, KisNode *node) { if (KisCloneLayer *clone = dynamic_cast(node)) { KIS_ASSERT_RECOVER_RETURN(clone->copyFrom()); const KisNode *newCopyFrom = findSymmetricClone(srcDuplicationRoot, dstDuplicationRoot, clone->copyFrom()); if (newCopyFrom) { KisLayer *newCopyFromLayer = qobject_cast(const_cast(newCopyFrom)); KIS_ASSERT_RECOVER_RETURN(newCopyFromLayer); clone->setCopyFrom(newCopyFromLayer); } } KisSafeReadNodeList::const_iterator iter; FOREACH_SAFE(iter, node->m_d->nodes) { KisNode *child = const_cast((*iter).data()); processDuplicatedClones(srcDuplicationRoot, dstDuplicationRoot, child); } } KisNode::KisNode() : m_d(new Private(this)) { m_d->parent = 0; m_d->graphListener = 0; moveToThread(qApp->thread()); } KisNode::KisNode(const KisNode & rhs) : KisBaseNode(rhs) , m_d(new Private(this)) { m_d->parent = 0; m_d->graphListener = 0; moveToThread(qApp->thread()); + // HACK ALERT: we create opacity channel in KisBaseNode, but we cannot + // initialize its node from there! So workaround it here! + QMap channels = rhs.keyframeChannels(); + for (auto it = channels.begin(); it != channels.end(); ++it) { + it.value()->setNode(this); + } + // NOTE: the nodes are not supposed to be added/removed while // creation of another node, so we do *no* locking here! KisSafeReadNodeList::const_iterator iter; FOREACH_SAFE(iter, rhs.m_d->nodes) { KisNodeSP child = (*iter)->clone(); child->createNodeProgressProxy(); m_d->nodes.append(child); child->setParent(this); } m_d->processDuplicatedClones(&rhs, this, this); } KisNode::~KisNode() { if (m_d->busyProgressIndicator) { m_d->busyProgressIndicator->prepareDestroying(); m_d->busyProgressIndicator->deleteLater(); } if (m_d->nodeProgressProxy) { m_d->nodeProgressProxy->prepareDestroying(); m_d->nodeProgressProxy->deleteLater(); } { QWriteLocker l(&m_d->nodeSubgraphLock); m_d->nodes.clear(); } delete m_d; } QRect KisNode::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } QRect KisNode::changeRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } QRect KisNode::accessRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } KisAbstractProjectionPlaneSP KisNode::projectionPlane() const { KIS_ASSERT_RECOVER_NOOP(0 && "KisNode::projectionPlane() is not defined!"); static KisAbstractProjectionPlaneSP plane = toQShared(new KisDumbProjectionPlane()); return plane; } KisProjectionLeafSP KisNode::projectionLeaf() const { return m_d->projectionLeaf; } bool KisNode::accept(KisNodeVisitor &v) { return v.visit(this); } void KisNode::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { visitor.visit(this, undoAdapter); } int KisNode::graphSequenceNumber() const { return m_d->graphListener ? m_d->graphListener->graphSequenceNumber() : -1; } KisNodeGraphListener *KisNode::graphListener() const { return m_d->graphListener; } void KisNode::setGraphListener(KisNodeGraphListener *graphListener) { m_d->graphListener = graphListener; QReadLocker l(&m_d->nodeSubgraphLock); KisSafeReadNodeList::const_iterator iter; FOREACH_SAFE(iter, m_d->nodes) { KisNodeSP child = (*iter); child->setGraphListener(graphListener); } } void KisNode::setParent(KisNodeWSP parent) { QWriteLocker l(&m_d->nodeSubgraphLock); m_d->parent = parent; } KisNodeSP KisNode::parent() const { QReadLocker l(&m_d->nodeSubgraphLock); return m_d->parent.isValid() ? KisNodeSP(m_d->parent) : KisNodeSP(); } KisBaseNodeSP KisNode::parentCallback() const { return parent(); } void KisNode::notifyParentVisibilityChanged(bool value) { QReadLocker l(&m_d->nodeSubgraphLock); KisSafeReadNodeList::const_iterator iter; FOREACH_SAFE(iter, m_d->nodes) { KisNodeSP child = (*iter); child->notifyParentVisibilityChanged(value); } } void KisNode::baseNodeChangedCallback() { if(m_d->graphListener) { m_d->graphListener->nodeChanged(this); } } void KisNode::baseNodeInvalidateAllFramesCallback() { if(m_d->graphListener) { m_d->graphListener->invalidateAllFrames(); } } void KisNode::addKeyframeChannel(KisKeyframeChannel *channel) { channel->setNode(this); KisBaseNode::addKeyframeChannel(channel); } KisNodeSP KisNode::firstChild() const { QReadLocker l(&m_d->nodeSubgraphLock); return !m_d->nodes.isEmpty() ? m_d->nodes.first() : 0; } KisNodeSP KisNode::lastChild() const { QReadLocker l(&m_d->nodeSubgraphLock); return !m_d->nodes.isEmpty() ? m_d->nodes.last() : 0; } KisNodeSP KisNode::prevChildImpl(KisNodeSP child) { /** * Warning: mind locking policy! * * The graph locks must be *always* taken in descending * order. That is if you want to (or it implicitly happens that * you) take a lock of a parent and a chil, you must first take * the lock of a parent, and only after that ask a child to do the * same. Otherwise you'll get a deadlock. */ QReadLocker l(&m_d->nodeSubgraphLock); int i = m_d->nodes.indexOf(child) - 1; return i >= 0 ? m_d->nodes.at(i) : 0; } KisNodeSP KisNode::nextChildImpl(KisNodeSP child) { /** * See a comment in KisNode::prevChildImpl() */ QReadLocker l(&m_d->nodeSubgraphLock); int i = m_d->nodes.indexOf(child) + 1; return i > 0 && i < m_d->nodes.size() ? m_d->nodes.at(i) : 0; } KisNodeSP KisNode::prevSibling() const { KisNodeSP parentNode = parent(); return parentNode ? parentNode->prevChildImpl(const_cast(this)) : 0; } KisNodeSP KisNode::nextSibling() const { KisNodeSP parentNode = parent(); return parentNode ? parentNode->nextChildImpl(const_cast(this)) : 0; } quint32 KisNode::childCount() const { QReadLocker l(&m_d->nodeSubgraphLock); return m_d->nodes.size(); } KisNodeSP KisNode::at(quint32 index) const { QReadLocker l(&m_d->nodeSubgraphLock); if (!m_d->nodes.isEmpty() && index < (quint32)m_d->nodes.size()) { return m_d->nodes.at(index); } return 0; } int KisNode::index(const KisNodeSP node) const { QReadLocker l(&m_d->nodeSubgraphLock); return m_d->nodes.indexOf(node); } QList KisNode::childNodes(const QStringList & nodeTypes, const KoProperties & properties) const { QReadLocker l(&m_d->nodeSubgraphLock); QList nodes; KisSafeReadNodeList::const_iterator iter; FOREACH_SAFE(iter, m_d->nodes) { if (*iter) { if (properties.isEmpty() || (*iter)->check(properties)) { bool rightType = true; if(!nodeTypes.isEmpty()) { rightType = false; Q_FOREACH (const QString &nodeType, nodeTypes) { if ((*iter)->inherits(nodeType.toLatin1())) { rightType = true; break; } } } if (rightType) { nodes.append(*iter); } } } } return nodes; } KisNodeSP KisNode::findChildByName(const QString &name) { KisNodeSP child = firstChild(); while (child) { if (child->name() == name) { return child; } if (child->childCount() > 0) { KisNodeSP grandChild = child->findChildByName(name); if (grandChild) { return grandChild; } } child = child->nextSibling(); } return 0; } bool KisNode::add(KisNodeSP newNode, KisNodeSP aboveThis) { Q_ASSERT(newNode); if (!newNode) return false; if (aboveThis && aboveThis->parent().data() != this) return false; if (!allowAsChild(newNode)) return false; if (newNode->parent()) return false; if (index(newNode) >= 0) return false; int idx = aboveThis ? this->index(aboveThis) + 1 : 0; // threoretical race condition may happen here ('idx' may become // deprecated until the write lock will be held). But we ignore // it, because it is not supported to add/remove nodes from two // concurrent threads simultaneously if (m_d->graphListener) { m_d->graphListener->aboutToAddANode(this, idx); } { QWriteLocker l(&m_d->nodeSubgraphLock); newNode->createNodeProgressProxy(); m_d->nodes.insert(idx, newNode); newNode->setParent(this); newNode->setGraphListener(m_d->graphListener); } if (m_d->graphListener) { m_d->graphListener->nodeHasBeenAdded(this, idx); } return true; } bool KisNode::remove(quint32 index) { if (index < childCount()) { KisNodeSP removedNode = at(index); if (m_d->graphListener) { m_d->graphListener->aboutToRemoveANode(this, index); } { QWriteLocker l(&m_d->nodeSubgraphLock); removedNode->setGraphListener(0); removedNode->setParent(0); // after calling aboutToRemoveANode or then the model get broken according to TT's modeltest m_d->nodes.removeAt(index); } if (m_d->graphListener) { m_d->graphListener->nodeHasBeenRemoved(this, index); } return true; } return false; } bool KisNode::remove(KisNodeSP node) { return node->parent().data() == this ? remove(index(node)) : false; } KisNodeProgressProxy* KisNode::nodeProgressProxy() const { if (m_d->nodeProgressProxy) { return m_d->nodeProgressProxy; } else if (parent()) { return parent()->nodeProgressProxy(); } return 0; } KisBusyProgressIndicator* KisNode::busyProgressIndicator() const { if (m_d->busyProgressIndicator) { return m_d->busyProgressIndicator; } else if (parent()) { return parent()->busyProgressIndicator(); } return 0; } void KisNode::createNodeProgressProxy() { if (!m_d->nodeProgressProxy) { m_d->nodeProgressProxy = new KisNodeProgressProxy(this); m_d->busyProgressIndicator = new KisBusyProgressIndicator(m_d->nodeProgressProxy); m_d->nodeProgressProxy->moveToThread(this->thread()); m_d->busyProgressIndicator->moveToThread(this->thread()); } } void KisNode::setDirty() { setDirty(extent()); } void KisNode::setDirty(const QVector &rects) { Q_FOREACH (const QRect &rc, rects) { setDirty(rc); } } void KisNode::setDirty(const QRegion ®ion) { setDirty(region.rects()); } void KisNode::setDirtyDontResetAnimationCache() { if(m_d->graphListener) { m_d->graphListener->requestProjectionUpdate(this, extent(), false); } } void KisNode::setDirty(const QRect & rect) { if(m_d->graphListener) { m_d->graphListener->requestProjectionUpdate(this, rect, true); } } void KisNode::invalidateFrames(const KisTimeRange &range, const QRect &rect) { if(m_d->graphListener) { m_d->graphListener->invalidateFrames(range, rect); } } void KisNode::requestTimeSwitch(int time) { if(m_d->graphListener) { m_d->graphListener->requestTimeSwitch(time); } } void KisNode::syncLodCache() { // noop. everything is done by getLodCapableDevices() } KisPaintDeviceList KisNode::getLodCapableDevices() const { KisPaintDeviceList list; KisPaintDeviceSP device = paintDevice(); if (device) { list << device; } KisPaintDeviceSP originalDevice = original(); if (originalDevice && originalDevice != device) { list << originalDevice; } list << projectionPlane()->getLodCapableDevices(); return list; } diff --git a/libs/image/kis_node_progress_proxy.h b/libs/image/kis_node_progress_proxy.h index 7e1257ce9b..5a4ccf3102 100644 --- a/libs/image/kis_node_progress_proxy.h +++ b/libs/image/kis_node_progress_proxy.h @@ -1,73 +1,73 @@ /* * Copyright (c) 2008 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_NODE_PROGRESS_PROXY_H_ #define _KIS_NODE_PROGRESS_PROXY_H_ #include #include #include #include "kritaimage_export.h" /** * This class implements \ref KoProgressProxy and allows node to report progress. */ class KRITAIMAGE_EXPORT KisNodeProgressProxy : public QObject, public KoProgressProxy { Q_OBJECT friend class KisNode; /** * Create a proxy to report progress when processing, this proxy is associated * with a node, it will report progress in the node progress bar. This proxy * will be deleted when @p _node is deleted. */ explicit KisNodeProgressProxy(KisNode* _node); ~KisNodeProgressProxy() override; public: - int maximum() const override ; + int maximum() const override; void setValue(int value) override; void setRange(int minimum, int maximum) override; void setFormat(const QString & format) override; /** * @return the current percentage (return -1 if no progress) */ int percentage() const; Q_SIGNALS: /** * Emitted when the percentage of the proxy is changed. * @param _percentage is the progress value in percent * @param _node is the node that own this \ref KisNodeProgressProxy */ void percentageChanged(int _percentage, const KisNodeSP& _node); private: /** * To be called when the node is and will be no longer available * and this object is going to be deleted as well. */ void prepareDestroying(); private: struct Private; Private* const d; }; #endif diff --git a/libs/image/kis_queues_progress_updater.cpp b/libs/image/kis_queues_progress_updater.cpp index d17dc4d3c8..6027da0b25 100644 --- a/libs/image/kis_queues_progress_updater.cpp +++ b/libs/image/kis_queues_progress_updater.cpp @@ -1,132 +1,138 @@ /* * 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_queues_progress_updater.h" #include #include #include struct Q_DECL_HIDDEN KisQueuesProgressUpdater::Private { Private(KisQueuesProgressUpdater *q) : timer(q) , startDelayTimer(q) , queueSizeMetric(0) , initialQueueSizeMetric(0) , progressProxy(0) , tickingRequested(false) { } QMutex mutex; QTimer timer; QTimer startDelayTimer; int queueSizeMetric; int initialQueueSizeMetric; QString jobName; KoProgressProxy *progressProxy; bool tickingRequested; static const int TIMER_INTERVAL = 500; static const int PROGRESS_DELAY = 1000; }; KisQueuesProgressUpdater::KisQueuesProgressUpdater(KoProgressProxy *progressProxy, QObject *parent) : QObject(parent), m_d(new Private(this)) { m_d->progressProxy = progressProxy; m_d->timer.setInterval(Private::TIMER_INTERVAL); m_d->timer.setSingleShot(false); connect(this, SIGNAL(sigStartTicking()), SLOT(startTicking()), Qt::QueuedConnection); connect(this, SIGNAL(sigStopTicking()), SLOT(stopTicking()), Qt::QueuedConnection); connect(&m_d->timer, SIGNAL(timeout()), SLOT(timerTicked())); m_d->startDelayTimer.setInterval(Private::PROGRESS_DELAY); m_d->startDelayTimer.setSingleShot(true); connect(&m_d->startDelayTimer, SIGNAL(timeout()), &m_d->timer, SLOT(start())); connect(&m_d->startDelayTimer, SIGNAL(timeout()), SLOT(timerTicked())); } KisQueuesProgressUpdater::~KisQueuesProgressUpdater() { delete m_d; } void KisQueuesProgressUpdater::updateProgress(int queueSizeMetric, const QString &jobName) { QMutexLocker locker(&m_d->mutex); m_d->queueSizeMetric = queueSizeMetric; if (queueSizeMetric && (jobName != m_d->jobName || m_d->queueSizeMetric > m_d->initialQueueSizeMetric)) { m_d->jobName = jobName; m_d->initialQueueSizeMetric = m_d->queueSizeMetric; } if (m_d->queueSizeMetric && !m_d->tickingRequested) { m_d->tickingRequested = true; emit sigStartTicking(); } else if (!m_d->queueSizeMetric && m_d->tickingRequested) { m_d->initialQueueSizeMetric = 0; m_d->jobName.clear(); m_d->tickingRequested = false; emit sigStopTicking(); } } void KisQueuesProgressUpdater::hide() { updateProgress(0, ""); } void KisQueuesProgressUpdater::startTicking() { m_d->startDelayTimer.start(); } void KisQueuesProgressUpdater::stopTicking() { m_d->startDelayTimer.stop(); m_d->timer.stop(); timerTicked(); } void KisQueuesProgressUpdater::timerTicked() { QMutexLocker locker(&m_d->mutex); - m_d->progressProxy->setRange(0, m_d->initialQueueSizeMetric); - m_d->progressProxy->setValue(m_d->initialQueueSizeMetric - m_d->queueSizeMetric); - m_d->progressProxy->setFormat(m_d->jobName); + if (!m_d->initialQueueSizeMetric) { + m_d->progressProxy->setRange(0, 100); + m_d->progressProxy->setValue(100); + m_d->progressProxy->setFormat("%p%"); + } else { + m_d->progressProxy->setRange(0, m_d->initialQueueSizeMetric); + m_d->progressProxy->setValue(m_d->initialQueueSizeMetric - m_d->queueSizeMetric); + m_d->progressProxy->setFormat(m_d->jobName); + } } diff --git a/libs/image/kis_raster_keyframe_channel.cpp b/libs/image/kis_raster_keyframe_channel.cpp index 25b4152f69..1877a809cb 100644 --- a/libs/image/kis_raster_keyframe_channel.cpp +++ b/libs/image/kis_raster_keyframe_channel.cpp @@ -1,296 +1,296 @@ /* * 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_raster_keyframe_channel.h" #include "kis_node.h" #include "kis_dom_utils.h" #include "kis_global.h" #include "kis_paint_device.h" #include "kis_paint_device_frames_interface.h" #include "kis_time_range.h" #include "kundo2command.h" #include "kis_onion_skin_compositor.h" struct KisRasterKeyframe : public KisKeyframe { KisRasterKeyframe(KisRasterKeyframeChannel *channel, int time, int frameId) : KisKeyframe(channel, time) , frameId(frameId) {} KisRasterKeyframe(const KisRasterKeyframe *rhs, KisKeyframeChannel *channel) : KisKeyframe(rhs, channel) , frameId(rhs->frameId) {} int frameId; KisKeyframeSP cloneFor(KisKeyframeChannel *channel) const override { return toQShared(new KisRasterKeyframe(this, channel)); } }; struct KisRasterKeyframeChannel::Private { Private(KisPaintDeviceWSP paintDevice, const QString filenameSuffix) : paintDevice(paintDevice), filenameSuffix(filenameSuffix), onionSkinsEnabled(false) {} KisPaintDeviceWSP paintDevice; QMap frameFilenames; QString filenameSuffix; bool onionSkinsEnabled; }; KisRasterKeyframeChannel::KisRasterKeyframeChannel(const KoID &id, const KisPaintDeviceWSP paintDevice, KisDefaultBoundsBaseSP defaultBounds) : KisKeyframeChannel(id, defaultBounds), m_d(new Private(paintDevice, QString())) { } -KisRasterKeyframeChannel::KisRasterKeyframeChannel(const KisRasterKeyframeChannel &rhs, const KisNodeWSP newParentNode, const KisPaintDeviceWSP newPaintDevice) +KisRasterKeyframeChannel::KisRasterKeyframeChannel(const KisRasterKeyframeChannel &rhs, KisNode *newParentNode, const KisPaintDeviceWSP newPaintDevice) : KisKeyframeChannel(rhs, newParentNode), m_d(new Private(newPaintDevice, rhs.m_d->filenameSuffix)) { KIS_ASSERT_RECOVER_NOOP(&rhs != this); m_d->frameFilenames = rhs.m_d->frameFilenames; m_d->onionSkinsEnabled = rhs.m_d->onionSkinsEnabled; } KisRasterKeyframeChannel::~KisRasterKeyframeChannel() { } int KisRasterKeyframeChannel::frameId(KisKeyframeSP keyframe) const { KisRasterKeyframe *key = dynamic_cast(keyframe.data()); Q_ASSERT(key != 0); return key->frameId; } int KisRasterKeyframeChannel::frameIdAt(int time) const { KisKeyframeSP activeKey = activeKeyframeAt(time); if (activeKey.isNull()) return -1; return frameId(activeKey); } void KisRasterKeyframeChannel::fetchFrame(KisKeyframeSP keyframe, KisPaintDeviceSP targetDevice) { m_d->paintDevice->framesInterface()->fetchFrame(frameId(keyframe), targetDevice); } void KisRasterKeyframeChannel::importFrame(int time, KisPaintDeviceSP sourceDevice, KUndo2Command *parentCommand) { KisKeyframeSP keyframe = addKeyframe(time, parentCommand); const int frame = frameId(keyframe); m_d->paintDevice->framesInterface()->uploadFrame(frame, sourceDevice); } QRect KisRasterKeyframeChannel::frameExtents(KisKeyframeSP keyframe) { return m_d->paintDevice->framesInterface()->frameBounds(frameId(keyframe)); } QString KisRasterKeyframeChannel::frameFilename(int frameId) const { return m_d->frameFilenames.value(frameId, QString()); } void KisRasterKeyframeChannel::setFilenameSuffix(const QString &suffix) { m_d->filenameSuffix = suffix; } void KisRasterKeyframeChannel::setFrameFilename(int frameId, const QString &filename) { Q_ASSERT(!m_d->frameFilenames.contains(frameId)); m_d->frameFilenames.insert(frameId, filename); } QString KisRasterKeyframeChannel::chooseFrameFilename(int frameId, const QString &layerFilename) { QString filename; if (m_d->frameFilenames.isEmpty()) { // Use legacy naming convention for first keyframe filename = layerFilename + m_d->filenameSuffix; } else { filename = layerFilename + m_d->filenameSuffix + ".f" + QString::number(frameId); } setFrameFilename(frameId, filename); return filename; } KisKeyframeSP KisRasterKeyframeChannel::createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) { KisRasterKeyframe *keyframe; if (!copySrc) { int frameId = m_d->paintDevice->framesInterface()->createFrame(false, 0, QPoint(), parentCommand); keyframe = new KisRasterKeyframe(this, time, frameId); } else { int srcFrame = frameId(copySrc); int frameId = m_d->paintDevice->framesInterface()->createFrame(true, srcFrame, QPoint(), parentCommand); KisRasterKeyframe *srcKeyframe = dynamic_cast(copySrc.data()); Q_ASSERT(srcKeyframe); keyframe = new KisRasterKeyframe(srcKeyframe, this); keyframe->setTime(time); keyframe->frameId = frameId; } return toQShared(keyframe); } void KisRasterKeyframeChannel::destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) { m_d->paintDevice->framesInterface()->deleteFrame(frameId(key), parentCommand); } void KisRasterKeyframeChannel::uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) { KisRasterKeyframeChannel *srcRasterChannel = dynamic_cast(srcChannel); KIS_ASSERT_RECOVER_RETURN(srcRasterChannel); const int srcId = srcRasterChannel->frameIdAt(srcTime); const int dstId = frameId(dstFrame); m_d->paintDevice->framesInterface()-> uploadFrame(srcId, dstId, srcRasterChannel->m_d->paintDevice); } QRect KisRasterKeyframeChannel::affectedRect(KisKeyframeSP key) { KeyframesMap::iterator it = keys().find(key->time()); QRect rect; // Calculate changed area as the union of the current and previous keyframe. // This makes sure there are no artifacts left over from the previous frame // where the new one doesn't cover the area. if (it == keys().begin()) { // Using the *next* keyframe at the start of the timeline avoids artifacts // when deleting or moving the first key it++; } else { it--; } if (it != keys().end()) { rect = m_d->paintDevice->framesInterface()->frameBounds(frameId(it.value())); } rect |= m_d->paintDevice->framesInterface()->frameBounds(frameId(key)); if (m_d->onionSkinsEnabled) { const QRect dirtyOnionSkinsRect = KisOnionSkinCompositor::instance()->calculateFullExtent(m_d->paintDevice); rect |= dirtyOnionSkinsRect; } return rect; } QDomElement KisRasterKeyframeChannel::toXML(QDomDocument doc, const QString &layerFilename) { m_d->frameFilenames.clear(); return KisKeyframeChannel::toXML(doc, layerFilename); } void KisRasterKeyframeChannel::loadXML(const QDomElement &channelNode) { m_d->frameFilenames.clear(); KisKeyframeChannel::loadXML(channelNode); } void KisRasterKeyframeChannel::saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) { int frame = frameId(keyframe); QString filename = frameFilename(frame); if (filename.isEmpty()) { filename = chooseFrameFilename(frame, layerFilename); } keyframeElement.setAttribute("frame", filename); QPoint offset = m_d->paintDevice->framesInterface()->frameOffset(frame); KisDomUtils::saveValue(&keyframeElement, "offset", offset); } KisKeyframeSP KisRasterKeyframeChannel::loadKeyframe(const QDomElement &keyframeNode) { int time = keyframeNode.attribute("time").toUInt(); QPoint offset; KisDomUtils::loadValue(keyframeNode, "offset", &offset); QString frameFilename = keyframeNode.attribute("frame"); KisKeyframeSP keyframe; if (m_d->frameFilenames.isEmpty()) { // First keyframe loaded: use the existing frame Q_ASSERT(keyframeCount() == 1); keyframe = constKeys().begin().value(); // Remove from keys. It will get reinserted with new time once we return keys().remove(keyframe->time()); keyframe->setTime(time); m_d->paintDevice->framesInterface()->setFrameOffset(frameId(keyframe), offset); } else { KUndo2Command tempCommand; int frameId = m_d->paintDevice->framesInterface()->createFrame(false, 0, offset, &tempCommand); keyframe = toQShared(new KisRasterKeyframe(this, time, frameId)); } setFrameFilename(frameId(keyframe), frameFilename); return keyframe; } bool KisRasterKeyframeChannel::hasScalarValue() const { return false; } void KisRasterKeyframeChannel::setOnionSkinsEnabled(bool value) { m_d->onionSkinsEnabled = value; } bool KisRasterKeyframeChannel::onionSkinsEnabled() const { return m_d->onionSkinsEnabled; } diff --git a/libs/image/kis_raster_keyframe_channel.h b/libs/image/kis_raster_keyframe_channel.h index ce0c08bc4d..a7cb20271c 100644 --- a/libs/image/kis_raster_keyframe_channel.h +++ b/libs/image/kis_raster_keyframe_channel.h @@ -1,91 +1,91 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_RASTER_KEYFRAME_CHANNEL_H #define _KIS_RASTER_KEYFRAME_CHANNEL_H #include "kis_keyframe_channel.h" class KRITAIMAGE_EXPORT KisRasterKeyframeChannel : public KisKeyframeChannel { Q_OBJECT public: KisRasterKeyframeChannel(const KoID& id, const KisPaintDeviceWSP paintDevice, KisDefaultBoundsBaseSP defaultBounds); - KisRasterKeyframeChannel(const KisRasterKeyframeChannel &rhs, const KisNodeWSP newParentNode, const KisPaintDeviceWSP newPaintDevice); + KisRasterKeyframeChannel(const KisRasterKeyframeChannel &rhs, KisNode *newParentNode, const KisPaintDeviceWSP newPaintDevice); ~KisRasterKeyframeChannel() override; public: /** * Return the ID of the active frame at a given time. The active frame is * defined by the keyframe at the given time or the last keyframe before it. * @param time * @return active frame id */ int frameIdAt(int time) const; /** * Copy the active frame at given time to target device. * @param keyframe keyframe to copy from * @param targetDevice device to copy the frame to */ void fetchFrame(KisKeyframeSP keyframe, KisPaintDeviceSP targetDevice); /** * Copy the content of the sourceDevice into a new keyframe at given time * @param time position of new keyframe * @param sourceDevice source for content */ void importFrame(int time, KisPaintDeviceSP sourceDevice, KUndo2Command *parentCommand); QRect frameExtents(KisKeyframeSP keyframe); QString frameFilename(int frameId) const; /** * When choosing filenames for frames, this will be appended to the node filename */ void setFilenameSuffix(const QString &suffix); bool hasScalarValue() const override; QDomElement toXML(QDomDocument doc, const QString &layerFilename) override; void loadXML(const QDomElement &channelNode) override; void setOnionSkinsEnabled(bool value); bool onionSkinsEnabled() const; protected: KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) override; void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) override; void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) override; QRect affectedRect(KisKeyframeSP key) override; void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) override; KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) override; private: void setFrameFilename(int frameId, const QString &filename); QString chooseFrameFilename(int frameId, const QString &layerFilename); int frameId(KisKeyframeSP keyframe) const; struct Private; QScopedPointer m_d; }; #endif diff --git a/libs/image/kis_scalar_keyframe_channel.cpp b/libs/image/kis_scalar_keyframe_channel.cpp index 045b32774b..6721e1f471 100644 --- a/libs/image/kis_scalar_keyframe_channel.cpp +++ b/libs/image/kis_scalar_keyframe_channel.cpp @@ -1,472 +1,485 @@ /* * 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_scalar_keyframe_channel.h" #include "kis_node.h" #include "kundo2command.h" #include "kis_time_range.h" #include #include struct KisScalarKeyframe : public KisKeyframe { KisScalarKeyframe(KisKeyframeChannel *channel, int time, qreal value) : KisKeyframe(channel, time) , value(value) {} KisScalarKeyframe(const KisScalarKeyframe *rhs, KisKeyframeChannel *channel) : KisKeyframe(rhs, channel) , value(rhs->value) {} qreal value; KisKeyframeSP cloneFor(KisKeyframeChannel *channel) const override { return toQShared(new KisScalarKeyframe(this, channel)); } }; KisScalarKeyframeChannel::AddKeyframeCommand::AddKeyframeCommand(KisScalarKeyframeChannel *channel, int time, qreal value, KUndo2Command *parentCommand) : KisReplaceKeyframeCommand(channel, time, channel->createKeyframe(time, value, parentCommand), parentCommand) {} struct KisScalarKeyframeChannel::Private { public: Private(qreal min, qreal max, KisKeyframe::InterpolationMode defaultInterpolation) : minValue(min), maxValue(max), firstFreeIndex(0), defaultInterpolation(defaultInterpolation) {} + Private(const Private &rhs) + : minValue(rhs.minValue), + maxValue(rhs.maxValue), + firstFreeIndex(rhs.firstFreeIndex), + defaultInterpolation(rhs.defaultInterpolation) + {} + qreal minValue; qreal maxValue; int firstFreeIndex; KisKeyframe::InterpolationMode defaultInterpolation; struct SetValueCommand; struct SetTangentsCommand; struct SetInterpolationModeCommand; }; KisScalarKeyframeChannel::KisScalarKeyframeChannel(const KoID &id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds, KisKeyframe::InterpolationMode defaultInterpolation) : KisKeyframeChannel(id, defaultBounds), m_d(new Private(minValue, maxValue, defaultInterpolation)) { } +KisScalarKeyframeChannel::KisScalarKeyframeChannel(const KisScalarKeyframeChannel &rhs, KisNode *newParentNode) + : KisKeyframeChannel(rhs, newParentNode), + m_d(new Private(*rhs.m_d)) +{ +} + KisScalarKeyframeChannel::~KisScalarKeyframeChannel() {} bool KisScalarKeyframeChannel::hasScalarValue() const { return true; } qreal KisScalarKeyframeChannel::minScalarValue() const { return m_d->minValue; } qreal KisScalarKeyframeChannel::maxScalarValue() const { return m_d->maxValue; } qreal KisScalarKeyframeChannel::scalarValue(const KisKeyframeSP keyframe) const { KisScalarKeyframe *key = dynamic_cast(keyframe.data()); Q_ASSERT(key != 0); return key->value; } struct KisScalarKeyframeChannel::Private::SetValueCommand : public KUndo2Command { SetValueCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, qreal oldValue, qreal newValue, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldValue(oldValue), m_newValue(newValue) { } void redo() override { setValue(m_newValue); } void undo() override { setValue(m_oldValue); } void setValue(qreal value) { KisScalarKeyframe *key = dynamic_cast(m_keyframe.data()); Q_ASSERT(key != 0); key->value = value; m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; qreal m_oldValue; qreal m_newValue; }; struct KisScalarKeyframeChannel::Private::SetTangentsCommand : public KUndo2Command { SetTangentsCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, KisKeyframe::InterpolationTangentsMode oldMode, QPointF oldLeftTangent, QPointF oldRightTangent, KisKeyframe::InterpolationTangentsMode newMode, QPointF newLeftTangent, QPointF newRightTangent, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldMode(oldMode), m_oldLeftTangent(oldLeftTangent), m_oldRightTangent(oldRightTangent), m_newMode(newMode), m_newLeftTangent(newLeftTangent), m_newRightTangent(newRightTangent) { } void redo() override { m_keyframe->setTangentsMode(m_newMode); m_keyframe->setInterpolationTangents(m_newLeftTangent, m_newRightTangent); m_channel->notifyKeyframeChanged(m_keyframe); } void undo() override { m_keyframe->setTangentsMode(m_oldMode); m_keyframe->setInterpolationTangents(m_oldLeftTangent, m_oldRightTangent); m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; KisKeyframe::InterpolationTangentsMode m_oldMode; QPointF m_oldLeftTangent; QPointF m_oldRightTangent; KisKeyframe::InterpolationTangentsMode m_newMode; QPointF m_newLeftTangent; QPointF m_newRightTangent; }; struct KisScalarKeyframeChannel::Private::SetInterpolationModeCommand : public KUndo2Command { SetInterpolationModeCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, KisKeyframe::InterpolationMode oldMode, KisKeyframe::InterpolationMode newMode, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldMode(oldMode), m_newMode(newMode) { } void redo() override { m_keyframe->setInterpolationMode(m_newMode); m_channel->notifyKeyframeChanged(m_keyframe); } void undo() override { m_keyframe->setInterpolationMode(m_oldMode); m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; KisKeyframe::InterpolationMode m_oldMode; KisKeyframe::InterpolationMode m_newMode; }; void KisScalarKeyframeChannel::setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } qreal oldValue = scalarValue(keyframe); KUndo2Command *cmd = new Private::SetValueCommand(this, keyframe, oldValue, value, parentCommand); cmd->redo(); } void KisScalarKeyframeChannel::setInterpolationMode(KisKeyframeSP keyframe, KisKeyframe::InterpolationMode mode, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } KisKeyframe::InterpolationMode oldMode = keyframe->interpolationMode(); KUndo2Command *cmd = new Private::SetInterpolationModeCommand(this, keyframe, oldMode, mode, parentCommand); cmd->redo(); } void KisScalarKeyframeChannel::setInterpolationTangents(KisKeyframeSP keyframe, KisKeyframe::InterpolationTangentsMode mode, QPointF leftTangent, QPointF rightTangent, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } KisKeyframe::InterpolationTangentsMode oldMode = keyframe->tangentsMode(); QPointF oldLeftTangent = keyframe->leftTangent(); QPointF oldRightTangent = keyframe->rightTangent(); KUndo2Command *cmd = new Private::SetTangentsCommand(this, keyframe, oldMode, oldLeftTangent, oldRightTangent, mode, leftTangent, rightTangent, parentCommand); cmd->redo(); } qreal cubicBezier(qreal p0, qreal delta1, qreal delta2, qreal p3, qreal t) { qreal p1 = p0 + delta1; qreal p2 = p3 + delta2; qreal c = 1-t; return c*c*c * p0 + 3*c*c*t * p1 + 3*c*t*t * p2 + t*t*t * p3; } void normalizeTangents(const QPointF point1, QPointF &rightTangent, QPointF &leftTangent, const QPointF point2) { // To ensure that the curve is monotonic wrt time, // check that control points lie between the endpoints. // If not, force them into range by scaling down the tangents float interval = point2.x() - point1.x(); if (rightTangent.x() < 0) rightTangent *= 0; if (leftTangent.x() > 0) leftTangent *= 0; if (rightTangent.x() > interval) { rightTangent *= interval / rightTangent.x(); } if (leftTangent.x() < -interval) { leftTangent *= interval / -leftTangent.x(); } } QPointF KisScalarKeyframeChannel::interpolate(QPointF point1, QPointF rightTangent, QPointF leftTangent, QPointF point2, qreal t) { normalizeTangents(point1, rightTangent, leftTangent, point2); qreal x = cubicBezier(point1.x(), rightTangent.x(), leftTangent.x(), point2.x(), t); qreal y = cubicBezier(point1.y(), rightTangent.y(), leftTangent.y(), point2.y(), t); return QPointF(x,y); } qreal findCubicCurveParameter(int time0, qreal delta0, qreal delta1, int time1, int time) { if (time == time0) return 0.0; if (time == time1) return 1.0; qreal min_t = 0.0; qreal max_t = 1.0; while (true) { qreal t = (max_t + min_t) / 2; qreal time_t = cubicBezier(time0, delta0, delta1, time1, t); if (time_t < time - 0.05) { min_t = t; } else if (time_t > time + 0.05) { max_t = t; } else { // Close enough return t; } } } qreal KisScalarKeyframeChannel::interpolatedValue(int time) const { KisKeyframeSP activeKey = activeKeyframeAt(time); if (activeKey.isNull()) return qQNaN(); KisKeyframeSP nextKey = nextKeyframe(activeKey); qreal result = qQNaN(); if (time == activeKey->time() || nextKey.isNull()) { result = scalarValue(activeKey); } else { switch (activeKey->interpolationMode()) { case KisKeyframe::Constant: result = scalarValue(activeKey); break; case KisKeyframe::Linear: { int time0 = activeKey->time(); int time1 = nextKey->time(); qreal value0 = scalarValue(activeKey); qreal value1 = scalarValue(nextKey); result = value0 + (value1 - value0) * (time - time0) / (time1 - time0); } break; case KisKeyframe::Bezier: { QPointF point0 = QPointF(activeKey->time(), scalarValue(activeKey)); QPointF point1 = QPointF(nextKey->time(), scalarValue(nextKey)); QPointF tangent0 = activeKey->rightTangent(); QPointF tangent1 = nextKey->leftTangent(); normalizeTangents(point0, tangent0, tangent1, point1); qreal t = findCubicCurveParameter(point0.x(), tangent0.x(), tangent1.x(), point1.x(), time); result = interpolate(point0, tangent0, tangent1, point1, t).y(); } break; default: KIS_ASSERT_RECOVER_BREAK(false); break; } } if (result > m_d->maxValue) return m_d->maxValue; if (result < m_d->minValue) return m_d->minValue; return result; } qreal KisScalarKeyframeChannel::currentValue() const { return interpolatedValue(currentTime()); } KisKeyframeSP KisScalarKeyframeChannel::createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) { if (copySrc) { KisScalarKeyframe *srcKeyframe = dynamic_cast(copySrc.data()); Q_ASSERT(srcKeyframe); KisScalarKeyframe *keyframe = new KisScalarKeyframe(srcKeyframe, this); keyframe->setTime(time); return toQShared(keyframe); } else { return createKeyframe(time, 0, parentCommand); } } KisKeyframeSP KisScalarKeyframeChannel::createKeyframe(int time, qreal value, KUndo2Command *parentCommand) { Q_UNUSED(parentCommand); KisScalarKeyframe *keyframe = new KisScalarKeyframe(this, time, value); keyframe->setInterpolationMode(m_d->defaultInterpolation); return toQShared(keyframe); } void KisScalarKeyframeChannel::destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) { Q_UNUSED(parentCommand); Q_UNUSED(key); } void KisScalarKeyframeChannel::uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) { KisScalarKeyframeChannel *srcScalarChannel = dynamic_cast(srcChannel); KIS_ASSERT_RECOVER_RETURN(srcScalarChannel); KisKeyframeSP srcFrame = srcScalarChannel->keyframeAt(srcTime); KIS_ASSERT_RECOVER_RETURN(srcFrame); KisScalarKeyframe *dstKey = dynamic_cast(dstFrame.data()); dstKey->value = srcChannel->scalarValue(srcFrame); notifyKeyframeChanged(dstFrame); } QRect KisScalarKeyframeChannel::affectedRect(KisKeyframeSP key) { Q_UNUSED(key); if (node()) { return node()->extent(); } else { return QRect(); } } void KisScalarKeyframeChannel::saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) { Q_UNUSED(layerFilename); keyframeElement.setAttribute("value", KisDomUtils::toString(scalarValue(keyframe))); QString interpolationMode; if (keyframe->interpolationMode() == KisKeyframe::Constant) interpolationMode = "constant"; if (keyframe->interpolationMode() == KisKeyframe::Linear) interpolationMode = "linear"; if (keyframe->interpolationMode() == KisKeyframe::Bezier) interpolationMode = "bezier"; QString tangentsMode; if (keyframe->tangentsMode() == KisKeyframe::Smooth) tangentsMode = "smooth"; if (keyframe->tangentsMode() == KisKeyframe::Sharp) tangentsMode = "sharp"; keyframeElement.setAttribute("interpolation", interpolationMode); keyframeElement.setAttribute("tangents", tangentsMode); KisDomUtils::saveValue(&keyframeElement, "leftTangent", keyframe->leftTangent()); KisDomUtils::saveValue(&keyframeElement, "rightTangent", keyframe->rightTangent()); } KisKeyframeSP KisScalarKeyframeChannel::loadKeyframe(const QDomElement &keyframeNode) { int time = keyframeNode.toElement().attribute("time").toUInt(); qreal value = KisDomUtils::toDouble(keyframeNode.toElement().attribute("value")); KUndo2Command tempParentCommand; KisKeyframeSP keyframe = createKeyframe(time, KisKeyframeSP(), &tempParentCommand); setScalarValue(keyframe, value); QString interpolationMode = keyframeNode.toElement().attribute("interpolation"); if (interpolationMode == "constant") { keyframe->setInterpolationMode(KisKeyframe::Constant); } else if (interpolationMode == "linear") { keyframe->setInterpolationMode(KisKeyframe::Linear); } else if (interpolationMode == "bezier") { keyframe->setInterpolationMode(KisKeyframe::Bezier); } QString tangentsMode = keyframeNode.toElement().attribute("tangents"); if (tangentsMode == "smooth") { keyframe->setTangentsMode(KisKeyframe::Smooth); } else if (tangentsMode == "sharp") { keyframe->setTangentsMode(KisKeyframe::Sharp); } QPointF leftTangent; QPointF rightTangent; KisDomUtils::loadValue(keyframeNode, "leftTangent", &leftTangent); KisDomUtils::loadValue(keyframeNode, "rightTangent", &rightTangent); keyframe->setInterpolationTangents(leftTangent, rightTangent); return keyframe; } void KisScalarKeyframeChannel::notifyKeyframeChanged(KisKeyframeSP keyframe) { QRect rect = affectedRect(keyframe); KisTimeRange range = affectedFrames(keyframe->time()); requestUpdate(range, rect); emit sigKeyframeChanged(keyframe); } diff --git a/libs/image/kis_scalar_keyframe_channel.h b/libs/image/kis_scalar_keyframe_channel.h index ee32a06017..4ee6a653eb 100644 --- a/libs/image/kis_scalar_keyframe_channel.h +++ b/libs/image/kis_scalar_keyframe_channel.h @@ -1,69 +1,70 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_SCALAR_KEYFRAME_CHANNEL_H #define _KIS_SCALAR_KEYFRAME_CHANNEL_H #include "kis_keyframe_channel.h" #include "kis_keyframe_commands.h" class KRITAIMAGE_EXPORT KisScalarKeyframeChannel : public KisKeyframeChannel { Q_OBJECT public: struct AddKeyframeCommand : public KisReplaceKeyframeCommand { AddKeyframeCommand(KisScalarKeyframeChannel *channel, int time, qreal value, KUndo2Command *parentCommand); }; KisScalarKeyframeChannel(const KoID& id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds, KisKeyframe::InterpolationMode defaultInterpolation=KisKeyframe::Constant); + KisScalarKeyframeChannel(const KisScalarKeyframeChannel &rhs, KisNode *newParentNode); ~KisScalarKeyframeChannel() override; bool hasScalarValue() const override; qreal minScalarValue() const override; qreal maxScalarValue() const override; qreal scalarValue(const KisKeyframeSP keyframe) const override; void setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand = 0) override; void setInterpolationMode(KisKeyframeSP keyframe, KisKeyframe::InterpolationMode mode, KUndo2Command *parentCommand = 0); void setInterpolationTangents(KisKeyframeSP keyframe, KisKeyframe::InterpolationTangentsMode, QPointF leftTangent, QPointF rightTangent, KUndo2Command *parentCommand); qreal interpolatedValue(int time) const; qreal currentValue() const; static QPointF interpolate(QPointF point1, QPointF rightTangent, QPointF leftTangent, QPointF point2, qreal t); protected: KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) override; KisKeyframeSP createKeyframe(int time, qreal value, KUndo2Command *parentCommand); void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) override; void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) override; QRect affectedRect(KisKeyframeSP key) override; void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) override; KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) override; private: void notifyKeyframeChanged(KisKeyframeSP keyframe); struct Private; QScopedPointer m_d; }; #endif diff --git a/libs/libkis/Action.cpp b/libs/libkis/Action.cpp index 8c42d9beee..152a47ac05 100644 --- a/libs/libkis/Action.cpp +++ b/libs/libkis/Action.cpp @@ -1,164 +1,164 @@ /* * 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 "Action.h" struct Action::Private { Private() {} QAction *action {0}; }; Action::Action(QObject *parent) : QObject(parent) , d(new Private) { d->action = new KisAction(this); connect(d->action, SIGNAL(triggered(bool)), SIGNAL(triggered(bool))); } Action::Action(const QString &name, QAction *action, QObject *parent) : QObject(parent) , d(new Private) { d->action = action; d->action->setObjectName(name); connect(d->action, SIGNAL(triggered(bool)), SIGNAL(triggered(bool))); } Action::~Action() { delete d; } bool Action::operator==(const Action &other) const { return (d->action == other.d->action); } bool Action::operator!=(const Action &other) const { return !(operator==(other)); } QString Action::text() const { if (!d->action) return ""; return d->action->text(); } -void Action::settext(QString text) +void Action::setText(QString text) { if (!d->action) return; d->action->setText(text); } QString Action::name() const { if (!d->action) return ""; return d->action->objectName(); } void Action::setName(QString name) { if (!d->action) return; d->action->setObjectName(name); } bool Action::isCheckable() const { if (!d->action) return false; return d->action->isCheckable(); } void Action::setCheckable(bool value) { if (!d->action) return; d->action->setCheckable(value); } bool Action::isChecked() const { if (!d->action) return false; return d->action->isChecked(); } void Action::setChecked(bool value) { if (!d->action) return; d->action->setChecked(value); } QString Action::shortcut() const { if (!d->action) return QString(); return d->action->shortcut().toString(); } void Action::setShortcut(QString value) { if (!d->action) return; d->action->setShortcut(QKeySequence::fromString(value)); } bool Action::isVisible() const { if (!d->action) return false; return d->action->isVisible(); } void Action::setVisible(bool value) { if (!d->action) return; d->action->setVisible(value); } bool Action::isEnabled() const { if (!d->action) return false; return d->action->isEnabled(); } void Action::setEnabled(bool value) { if (!d->action) return; d->action->setEnabled(value); } void Action::setToolTip(QString tooltip) { if (!d->action) return; d->action->setToolTip(tooltip); } void Action::trigger() { d->action->trigger(); } void Action::setMenu(const QString menu) { d->action->setProperty("menu", menu); } QString Action::menu() const { return d->action->property("menu").toString(); } diff --git a/libs/libkis/Action.h b/libs/libkis/Action.h index e20d20371f..66ed921131 100644 --- a/libs/libkis/Action.h +++ b/libs/libkis/Action.h @@ -1,160 +1,160 @@ /* * 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_ACTION_H #define LIBKIS_ACTION_H #include #include "kritalibkis_export.h" #include "libkis.h" /** * Action encapsulates a KisAction. By default, actions are put in the Tools/Scripts menu. */ class KRITALIBKIS_EXPORT Action : public QObject { Q_OBJECT public: /** * @brief Action Create a new action object * @param parent the parent if it's in a QObject hierarchy */ explicit Action(QObject *parent = 0); /** * @brief Action Create a new action object * @param name the name of the action * @param action the QAction it wraps * @param parent the parent if it's in a QObject hierarchy */ Action(const QString &name, QAction *action, QObject *parent = 0); ~Action() override; bool operator==(const Action &other) const; bool operator!=(const Action &other) const; public Q_SLOTS: /** * @return the user-visible text of the action. */ QString text() const; /** * set the user-visible text of the action to @param text. */ - void settext(QString text); + void setText(QString text); /** * @return the internal name of the action. */ QString name() const; /** * set the name of the action to @param name. This is not the user-visible name, but the internal one */ void setName(QString name); /** * @return true if the action is checkable, false if it isn't* */ bool isCheckable() const; /** * Set the action action checkable if @param value is true, unchecked if it's false */ void setCheckable(bool value); /** * @return true if the action is checked, false if it isn't */ bool isChecked() const; /** * Set the action checked if @param value is true, unchecked if it's false */ void setChecked(bool value); /** * Return the action's shortcut as a string */ QString shortcut() const; /** * set the action's shortcut to the given string. * @code * action.setShortcut("CTRL+SHIFT+S") * @endcode */ void setShortcut(QString value); bool isVisible() const; /** * @brief setVisible determines whether the action will be visible in the scripting menu. * @param value true if the action is to be shown in the menu, false otherwise */ void setVisible(bool value); /** * @return true if the action is enabled, false if not */ bool isEnabled() const; /** * Set the action enabled or disabled according to @param value */ void setEnabled(bool value); /** * Set the tooltip to the given @param tooltip */ void setToolTip(QString tooltip); /** * Trigger this action */ void trigger(); /** * @brief setMenu determines in which menu the action will be placed. The default is tools/scripts * @param menu the menu where the action should go, / -separated to drill down the hierarchy */ void setMenu(const QString menu); /** * @return the menu in which this action is to be placed. */ QString menu() const; Q_SIGNALS: /** * Emitted whenever the action is triggered. */ void triggered(bool); private: struct Private; Private *const d; }; #endif // LIBKIS_ACTION_H diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index 3849f478c6..caa307529a 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,549 +1,604 @@ /* * 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; 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); + resizeImage(d->document->image()->bounds().x(), + d->document->image()->bounds().y(), + 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()); + resizeImage(d->document->image()->bounds().x(), + d->document->image()->bounds().y(), + value, + d->document->image()->height()); +} + + +int Document::xOffset() const +{ + if (!d->document) return 0; + KisImageSP image = d->document->image(); + if (!image) return 0; + return image->bounds().x(); +} + +void Document::setXOffset(int x) +{ + if (!d->document) return; + if (!d->document->image()) return; + resizeImage(x, + d->document->image()->bounds().y(), + d->document->image()->width(), + d->document->image()->height()); +} + + +int Document::yOffset() const +{ + if (!d->document) return 0; + KisImageSP image = d->document->image(); + if (!image) return 0; + return image->bounds().y(); +} + +void Document::setYOffset(int y) +{ + if (!d->document) return; + if (!d->document->image()) return; + resizeImage(d->document->image()->bounds().x(), + y, + d->document->image()->width(), + 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(); } } KisPart::instance()->removeDocument(d->document); 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()); + + const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename); + const QByteArray outputFormat = outputFormatString.toLatin1(); + + return d->document->exportDocument(QUrl::fromLocalFile(filename), outputFormat, 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) +void Document::resizeImage(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; - QRect rc = image->bounds(); + QRect rc; + rc.setX(x); + rc.setY(y); 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(); + return d->document->save(true, 0); } bool Document::saveAs(const QString &filename) { if (!d->document) return false; - return d->document->saveAs(QUrl::fromLocalFile(filename)); + + const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename); + const QByteArray outputFormat = outputFormatString.toLatin1(); + + return d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true); } 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 e17a52db01..640f3c687c 100644 --- a/libs/libkis/Document.h +++ b/libs/libkis/Document.h @@ -1,514 +1,570 @@ /* * 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 the left edge of the canvas in pixels. + */ + int xOffset() const; + + /** + * @brief setXOffset sets the left edge of the canvas to @param x. + */ + void setXOffset(int x); + + /** + * @return the top edge of the canvas in pixels. + */ + int yOffset() const; + + /** + * @brief setYOffset sets the top edge of the canvas to @param y. + */ + void setYOffset(int y); + /** * @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 + * @param exportConfiguration a configuration object appropriate to the file format. + * An InfoObject will used to that configuration. + * + * The supported formats have specific configurations that must be used when in + * batchmode. They are described below: + * + *\b png + *
    + *
  • alpha: bool (True or False) + *
  • compression: int (1 to 9) + *
  • forceSRGB: bool (True or False) + *
  • indexed: bool (True or False) + *
  • interlaced: bool (True or False) + *
  • saveSRGBProfile: bool (True or False) + *
  • transparencyFillcolor: rgb (Ex:[255,255,255]) + *
+ * + *\b jpeg + *
    + *
  • baseline: bool (True or False) + *
  • exif: bool (True or False) + *
  • filters: bool (['ToolInfo', 'Anonymizer']) + *
  • forceSRGB: bool (True or False) + *
  • iptc: bool (True or False) + *
  • is_sRGB: bool (True or False) + *
  • optimize: bool (True or False) + *
  • progressive: bool (True or False) + *
  • quality: int (0 to 100) + *
  • saveProfile: bool (True or False) + *
  • smoothing: int (0 to 100) + *
  • subsampling: int (0 to 3) + *
  • transparencyFillcolor: rgb (Ex:[255,255,255]) + *
  • xmp: bool (True or False) + *
* @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 resizes the canvas to the given width and height. + * @brief resizeImage resizes the canvas to the given left edge, top edge, width and height. * Note: This doesn't scale, use scale image for that. + * @param x the new left edge + * @param y the new top edge * @param w the new width * @param h the new height */ - void resizeImage(int w, int h); + void resizeImage(int x, int y, 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/Filter.cpp b/libs/libkis/Filter.cpp index 6df2c286ca..56c1423af6 100644 --- a/libs/libkis/Filter.cpp +++ b/libs/libkis/Filter.cpp @@ -1,168 +1,165 @@ /* * 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 "Filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "Krita.h" #include "Document.h" #include "InfoObject.h" #include "Node.h" struct Filter::Private { Private() {} QString name; InfoObject *configuration {0}; }; Filter::Filter() : QObject(0) , d(new Private) { } Filter::~Filter() { delete d->configuration; delete d; } bool Filter::operator==(const Filter &other) const { return (d->name == other.d->name && d->configuration == other.d->configuration); } bool Filter::operator!=(const Filter &other) const { return !(operator==(other)); } QString Filter::name() const { return d->name; } void Filter::setName(const QString &name) { d->name = name; delete d->configuration; KisFilterSP filter = KisFilterRegistry::instance()->value(d->name); d->configuration = new InfoObject(filter->defaultConfiguration()); } InfoObject* Filter::configuration() const { return d->configuration; } void Filter::setConfiguration(InfoObject* value) { d->configuration = value; } bool Filter::apply(Node *node, int x, int y, int w, int h) { if (node->locked()) return false; KisFilterSP filter = KisFilterRegistry::instance()->value(d->name); if (!filter) return false; KisPaintDeviceSP dev = node->paintDevice(); if (!dev) return false; QRect applyRect = QRect(x, y, w, h); KisFilterConfigurationSP config = static_cast(d->configuration->configuration().data()); filter->process(dev, applyRect, config); return true; } bool Filter::startFilter(Node *node, int x, int y, int w, int h) { if (node->locked()) return false; KisFilterSP filter = KisFilterRegistry::instance()->value(d->name); if (!filter) return false; KisImageWSP image = node->image(); if (!image) return false; KisFilterConfigurationSP filterConfig = static_cast(d->configuration->configuration().data()); image->waitForDone(); QRect initialApplyRect = QRect(x, y, w, h); QRect applyRect = initialApplyRect; KisPaintDeviceSP paintDevice = node->paintDevice(); if (paintDevice && filter->needsTransparentPixels(filterConfig.data(), paintDevice->colorSpace())) { applyRect |= image->bounds(); } KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, node->node()); Document *document = Krita::instance()->activeDocument(); if (document && KisPart::instance()->viewCount(document->document()) > 0) { Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->document() == document->document()) { resources = new KisResourcesSnapshot(image, node->node(), view->resourceProvider()->resourceManager()); break; } } } delete document; KisStrokeId currentStrokeId = image->startStroke(new KisFilterStrokeStrategy(filter, KisFilterConfigurationSP(filterConfig), resources)); QRect processRect = filter->changedRect(applyRect, filterConfig.data(), 0); processRect &= image->bounds(); if (filter->supportsThreading()) { QSize size = KritaUtils::optimalPatchSize(); QVector rects = KritaUtils::splitRectIntoPatches(processRect, size); Q_FOREACH (const QRect &rc, rects) { image->addJob(currentStrokeId, new KisFilterStrokeStrategy::Data(rc, true)); } } else { image->addJob(currentStrokeId, new KisFilterStrokeStrategy::Data(processRect, false)); } image->endStroke(currentStrokeId); return true; } - - - diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp index 055a29d82a..50c0b32cca 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,394 +1,425 @@ /* * 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 #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(); std::sort(ls.begin(), ls.end()); 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::colorModels() const +{ + QSet colorModelsIds; + QList ids = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); + Q_FOREACH(KoID id, ids) { + colorModelsIds << id.id(); + } + return colorModelsIds.toList();; +} + +QStringList Krita::colorDepths(const QString &colorModel) const +{ + QSet colorDepthsIds; + QList ids = KoColorSpaceRegistry::instance()->colorDepthList(colorModel, KoColorSpaceRegistry::AllColorSpaces); + Q_FOREACH(KoID id, ids) { + colorDepthsIds << id.id(); + } + return colorDepthsIds.toList();; +} + 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; } +QStringList Krita::recentDocuments() const +{ + KConfigGroup grp = KSharedConfig::openConfig()->group(QString("RecentFiles")); + QStringList keys = grp.keyList(); + QStringList recentDocuments; + + for(int i = 0; i <= keys.filter("File").count(); i++) + recentDocuments << grp.readEntry(QString("File%1").arg(i), QString("")); + + return recentDocuments; +} + 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); + document->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); return new Document(document); } Window* Krita::openWindow() { KisMainWindow *mw = KisPart::instance()->createMainWindow(); return new Window(mw); } - Action *Krita::createAction(const QString &id, const QString &text) { KisAction *action = new KisAction(text, this); action->setObjectName(id); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); actionRegistry->propertizeAction(action->objectName(), action); bool ok; // We will skip this check int activationFlags = actionRegistry->getActionProperty(id, "activationFlags").toInt(&ok, 2); int activationConditions = actionRegistry->getActionProperty(id, "activationConditions").toInt(&ok, 2); action->setActivationFlags((KisAction::ActivationFlags) activationFlags); action->setActivationConditions((KisAction::ActivationConditions) activationConditions); 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 a132d997c6..389773b7fb 100644 --- a/libs/libkis/Krita.h +++ b/libs/libkis/Krita.h @@ -1,303 +1,325 @@ /* * 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 colorModels creates a list with all color models id's registered. + * @return a list of all color models or a empty list if there is no such color models. + */ + QStringList colorModels() const; + + /** + * @brief colorDepths creates a list with the names of all color depths + * compatible with the given color model. + * @param colorModel the id of a color model. + * @return a list of all color depths or a empty list if there is no such + * color depths. + */ + QStringList colorDepths(const QString &colorModel) 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 return all recent documents registered in the RecentFiles group of the kritarc + */ + QStringList recentDocuments() 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. This means that actions need to be created in the * setup phase of the plugin, not on the fly. * @param id the unique id for this action * @param text the user-visible text * @return the Action you can connect a slot to. */ Action *createAction(const QString &name, 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 4aff84e937..747740f112 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,568 +1,567 @@ /* * 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"; } + return QString(); } 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);; + QString mimeType = 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)); + bool r = doc->exportDocumentSync(QUrl::fromLocalFile(filename), mimeType.toLatin1()); 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/Palette.cpp b/libs/libkis/Palette.cpp index 1113fb3be2..02270710ae 100644 --- a/libs/libkis/Palette.cpp +++ b/libs/libkis/Palette.cpp @@ -1,119 +1,158 @@ /* * Copyright (c) 2017 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 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 "Palette.h" #include #include struct Palette::Private { KoColorSet *palette {0}; }; Palette::Palette(Resource *resource): d(new Private()) { d->palette = dynamic_cast(resource->resource()); } Palette::~Palette() { delete d; } int Palette::numberOfEntries() const { if (!d->palette) return 0; return d->palette->nColors(); } int Palette::columnCount() { if (!d->palette) return 0; return d->palette->columnCount(); } void Palette::setColumnCount(int columns) { if (d->palette) d->palette->setColumnCount(columns); } QString Palette::comment() { if (!d->palette) return ""; return d->palette->comment(); } void Palette::setComment(QString comment) { if (!d->palette) return; return d->palette->setComment(comment); } QStringList Palette::groupNames() { if (!d->palette) return QStringList(); return d->palette->getGroupNames(); } bool Palette::addGroup(QString name) { if (!d->palette) return false; return d->palette->addGroup(name); } bool Palette::removeGroup(QString name, bool keepColors) { if (!d->palette) return false; return d->palette->removeGroup(name, keepColors); } int Palette::colorsCountTotal() { if (!d->palette) return 0; return d->palette->nColors(); } int Palette::colorsCountGroup(QString name) { if (!d->palette) return 0; return d->palette->nColorsGroup(name); } KoColorSetEntry Palette::colorSetEntryByIndex(int index) { if (!d->palette) return KoColorSetEntry(); return d->palette->getColorGlobal(index); } KoColorSetEntry Palette::colorSetEntryFromGroup(int index, const QString &groupName) { if (!d->palette) return KoColorSetEntry(); return d->palette->getColorGroup(index, groupName); } ManagedColor *Palette::colorForEntry(KoColorSetEntry entry) { if (!d->palette) return 0; ManagedColor *color = new ManagedColor(entry.color); return color; } +void Palette::addEntry(KoColorSetEntry entry, QString groupName) +{ + d->palette->add(entry, groupName); +} + +void Palette::removeEntry(int index, const QString &groupName) +{ + d->palette->removeAt(index, groupName); +} + +void Palette::insertEntry(int index, KoColorSetEntry entry, QString groupName) +{ + d->palette->insertBefore(entry, index, groupName); +} + +bool Palette::editEntry(int index, KoColorSetEntry entry, QString groupName) +{ + return d->palette->changeColorSetEntry(entry, groupName, index); +} + +bool Palette::changeGroupName(QString oldGroupName, QString newGroupName) +{ + return d->palette->changeGroupName(oldGroupName, newGroupName); +} + +bool Palette::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) +{ + return d->palette->moveGroup(groupName, groupNameInsertBefore); +} + +bool Palette::save() +{ + if (d->palette->filename().size()>0) { + return d->palette->save(); + } + //if there's no filename the palette proly doesn't even exist... + return false; +} + KoColorSet *Palette::colorSet() { return d->palette; } diff --git a/libs/libkis/Palette.h b/libs/libkis/Palette.h index aaaa858660..9246763b29 100644 --- a/libs/libkis/Palette.h +++ b/libs/libkis/Palette.h @@ -1,141 +1,204 @@ /* * Copyright (c) 2017 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 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_PALETTE_H #define LIBKIS_PALETTE_H #include #include "kritalibkis_export.h" #include "libkis.h" #include "Resource.h" #include "KoColorSet.h" class ManagedColor; /** * @brief The Palette class * Palette is a resource object that stores organised color data. * It's purpose is to allow artists to save colors and store them. * * An example for printing all the palettes and the entries: * * @code import sys from krita import * resources = Application.resources("palette") for (k, v) in resources.items(): print(k) palette = Palette(v) for x in range(palette.numberOfEntries()): entry = palette.colorSetEntryByIndex(x) c = palette.colorForEntry(entry); print(x, entry.name, entry.id, entry.spotColor, c.toQString()) * @endcode */ class KRITALIBKIS_EXPORT Palette : public QObject { public: Palette(Resource *resource); ~Palette() override; /** * @brief numberOfEntries * @return */ int numberOfEntries() const; /** * @brief columnCount * @return the amount of columns this palette is set to use. */ int columnCount(); /** * @brief setColumnCount * Set the amount of columns this palette should use. */ void setColumnCount(int columns); /** * @brief comment * @return the comment or description associated with the palette. */ QString comment(); /** * @brief setComment * set the comment or description associated with the palette. * @param comment */ void setComment(QString comment); /** * @brief groupNames * @return the list of group names. This is list is in the order these groups are in the file. */ QStringList groupNames(); /** * @brief addGroup * @param name of the new group * @return whether adding the group was succesful. */ bool addGroup(QString name); /** * @brief removeGroup * @param name the name of the group to remove. * @param keepColors whether or not to delete all the colors inside, or to move them to the default group. * @return */ bool removeGroup(QString name, bool keepColors = true); /** * @brief colorsCountTotal * @return the total amount of entries in the whole group */ int colorsCountTotal(); /** * @brief colorsCountGroup * @param name of the group to check. Empty is the default group. * @return the amount of colors within that group. */ int colorsCountGroup(QString name); + /** + * @brief colorSetEntryByIndex + * get the colorsetEntry from the global index. + * @param index the global index + * @return the colorset entry + */ KoColorSetEntry colorSetEntryByIndex(int index); + /** + * @brief colorSetEntryFromGroup + * @param index index in the group. + * @param groupName the name of the group to get the color from. + * @return the colorsetentry. + */ KoColorSetEntry colorSetEntryFromGroup(int index, const QString &groupName); + /** + * @brief colorForEntry + * special function to retreive a ManagedColor object from the colorsetentry. + * @param entry the entry + * @return the ManagedColorObject + */ ManagedColor *colorForEntry(KoColorSetEntry entry); + /** + * @brief addEntry + * add an entry to a group. Gets appended to the end. + * @param entry the entry + * @param groupName the name of the group to add to. + */ + void addEntry(KoColorSetEntry entry, QString groupName = QString()); + /** + * @brief removeEntry + * remove the entry at @param index from the group @param groupName. + */ + void removeEntry(int index, const QString &groupName); + /** + * @brief insertEntry + * like addentry, but allows you to pick the index to insertBefore. + * @param index + * @param entry + * @param groupName + */ + void insertEntry(int index, KoColorSetEntry entry, QString groupName = QString()); + /** + * @brief editEntry + * Changes the entry at @param index by replacing it with @param entry. + * @param groupName the group at which the index is. + * @return whether it was succesful. + */ + bool editEntry (int index, KoColorSetEntry entry, QString groupName = QString()); + /** + * @brief changeGroupName + * change the group name. + * @param oldGroupName the old groupname to change. + * @param newGroupName the new name to change it into. + * @return whether succesful. Reasons for failure include not knowing have oldGroupName + */ + bool changeGroupName(QString oldGroupName, QString newGroupName); + /** + * @brief moveGroup + * move the group to before groupNameInsertBefore. + * @param groupName group to move. + * @param groupNameInsertBefore group to inset before. + * @return whether succesful. Reasons for failure include either group not existing. + */ + bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore = QString()); - //getcolorgroup - //Add - //Remove - //Insert + /** + * @brief save + * save the palette + * @return whether it was succesful. + */ + bool save(); private: friend class PaletteView; struct Private; Private *const d; /** * @brief colorSet * @return gives qa KoColorSet object back */ KoColorSet *colorSet(); }; #endif // LIBKIS_PALETTE_H diff --git a/libs/libkis/Window.cpp b/libs/libkis/Window.cpp index d06d8ab58c..e5b2189e77 100644 --- a/libs/libkis/Window.cpp +++ b/libs/libkis/Window.cpp @@ -1,121 +1,117 @@ /* * 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 "Window.h" #include #include #include #include #include struct Window::Private { Private() {} QPointer window; }; Window::Window(KisMainWindow *window, QObject *parent) : QObject(parent) , d(new Private) { d->window = window; connect(window, SIGNAL(destroyed(QObject*)), SIGNAL(windowClosed())); } Window::~Window() { delete d; } bool Window::operator==(const Window &other) const { return (d->window == other.d->window); } bool Window::operator!=(const Window &other) const { return !(operator==(other)); } QMainWindow *Window::qwindow() const { return d->window; } QList Window::views() const { QList ret; if (d->window) { foreach(QPointer view, KisPart::instance()->views()) { if (view->mainWindow() == d->window) { ret << new View(view); } } } return ret; } View *Window::addView(Document *document) { if (d->window) { - KisView *view = KisPart::instance()->createView(document->document(), - d->window->resourceManager(), - d->window->actionCollection(), - d->window); - d->window->addView(view); + KisView *view = d->window->newView(document->document()); return new View(view); } return 0; } void Window::showView(View *view) { if (views().contains(view)) { KisView *v = view->view(); d->window->showView(v); } } View *Window::activeView() const { if (d->window) { return new View(d->window->activeView()); } return 0; } void Window::activate() { if (d->window) { d->window->activateWindow(); } } void Window::close() { if (d->window) { KisPart::instance()->removeMainWindow(d->window); d->window->close(); } } diff --git a/libs/odf/KoDocumentBase.h b/libs/odf/KoDocumentBase.h index 9ce0d7c9a5..570b72534b 100644 --- a/libs/odf/KoDocumentBase.h +++ b/libs/odf/KoDocumentBase.h @@ -1,115 +1,104 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2005 David Faure Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2009 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KODOCUMENTBASE_H #define KODOCUMENTBASE_H class KoStore; class KoOdfReadStore; class KoOdfWriteStore; class KoEmbeddedDocumentSaver; class QUrl; class QByteArray; class QString; #include "kritaodf_export.h" /** * Base class for documents that can load and save ODF. Most of the * implementation is still in KoDocument, though that should probably * change. */ class KRITAODF_EXPORT KoDocumentBase { public: // context passed on saving to saveOdf struct SavingContext { SavingContext(KoOdfWriteStore &odfStore, KoEmbeddedDocumentSaver &embeddedSaver) : odfStore(odfStore) , embeddedSaver(embeddedSaver) {} KoOdfWriteStore &odfStore; KoEmbeddedDocumentSaver &embeddedSaver; }; /** * create a new KoDocumentBase */ KoDocumentBase(); /** * delete this document */ virtual ~KoDocumentBase(); /** * @return the current URL */ virtual QUrl url() const = 0; virtual void setUrl(const QUrl &url) = 0; /** * Checks whether the document is currently in the process of autosaving */ virtual bool isAutosaving() const = 0; /** * Returns true if this document or any of its internal child documents are modified. */ virtual bool isModified() const = 0; /** * Returns the actual mimetype of the document */ virtual QByteArray mimeType() const = 0; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ virtual void setMimeType(const QByteArray & mimeType) = 0; virtual QString localFilePath() const = 0; - /** - * @brief Set the format in which the document should be saved. - * - * This is called on loading, and in "save as", so you shouldn't - * have to call it. - * - * @param mimeType the mime type (format) to use. - */ - virtual void setOutputMimeType(const QByteArray & mimeType) = 0; - - virtual QByteArray outputMimeType() const = 0; private: class Private; Private *const d; }; #endif diff --git a/libs/odf/KoDocumentInfo.cpp b/libs/odf/KoDocumentInfo.cpp index 918a08249e..3a309f09b4 100644 --- a/libs/odf/KoDocumentInfo.cpp +++ b/libs/odf/KoDocumentInfo.cpp @@ -1,427 +1,438 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis Copyright (C) 2004 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 "KoDocumentInfo.h" #include "KoDocumentBase.h" #include "KoOdfWriteStore.h" #include "KoXmlNS.h" #include #include #include #include #include #include #include #include #include #include #include #include KoDocumentInfo::KoDocumentInfo(QObject *parent) : QObject(parent) { m_aboutTags << "title" << "description" << "subject" << "abstract" << "keyword" << "initial-creator" << "editing-cycles" << "editing-time" << "date" << "creation-date" << "language"; m_authorTags << "creator" << "initial" << "author-title" << "email" << "telephone" << "telephone-work" << "fax" << "country" << "postal-code" << "city" << "street" << "position" << "company"; setAboutInfo("editing-cycles", "0"); setAboutInfo("time-elapsed", "0"); setAboutInfo("initial-creator", i18n("Unknown")); setAboutInfo("creation-date", QDateTime::currentDateTime() .toString(Qt::ISODate)); } +KoDocumentInfo::KoDocumentInfo(const KoDocumentInfo &rhs, QObject *parent) + : QObject(parent), + m_aboutTags(rhs.m_aboutTags), + m_authorTags(rhs.m_authorTags), + m_authorInfo(rhs.m_authorInfo), + m_authorInfoOverride(rhs.m_authorInfoOverride), + m_aboutInfo(rhs.m_aboutInfo), + m_generator(rhs.m_generator) +{ +} + KoDocumentInfo::~KoDocumentInfo() { } bool KoDocumentInfo::load(const KoXmlDocument &doc) { m_authorInfo.clear(); if (!loadAboutInfo(doc.documentElement())) return false; if (!loadAuthorInfo(doc.documentElement())) return false; return true; } QDomDocument KoDocumentInfo::save(QDomDocument &doc) { updateParametersAndBumpNumCycles(); QDomElement s = saveAboutInfo(doc); if (!s.isNull()) doc.documentElement().appendChild(s); s = saveAuthorInfo(doc); if (!s.isNull()) doc.documentElement().appendChild(s); if (doc.documentElement().isNull()) return QDomDocument(); return doc; } void KoDocumentInfo::setAuthorInfo(const QString &info, const QString &data) { if (!m_authorTags.contains(info)) { return; } m_authorInfoOverride.insert(info, data); } void KoDocumentInfo::setActiveAuthorInfo(const QString &info, const QString &data) { if (!m_authorTags.contains(info)) { return; } if (data.isEmpty()) { m_authorInfo.remove(info); } else { m_authorInfo.insert(info, data); } emit infoUpdated(info, data); } QString KoDocumentInfo::authorInfo(const QString &info) const { if (!m_authorTags.contains(info)) return QString(); return m_authorInfo[ info ]; } void KoDocumentInfo::setAboutInfo(const QString &info, const QString &data) { if (!m_aboutTags.contains(info)) return; m_aboutInfo.insert(info, data); emit infoUpdated(info, data); } QString KoDocumentInfo::aboutInfo(const QString &info) const { if (!m_aboutTags.contains(info)) { return QString(); } return m_aboutInfo[info]; } bool KoDocumentInfo::saveOasisAuthorInfo(KoXmlWriter &xmlWriter) { Q_FOREACH (const QString & tag, m_authorTags) { if (!authorInfo(tag).isEmpty() && tag == "creator") { xmlWriter.startElement("dc:creator"); xmlWriter.addTextNode(authorInfo("creator")); xmlWriter.endElement(); } else if (!authorInfo(tag).isEmpty()) { xmlWriter.startElement("meta:user-defined"); xmlWriter.addAttribute("meta:name", tag); xmlWriter.addTextNode(authorInfo(tag)); xmlWriter.endElement(); } } return true; } bool KoDocumentInfo::loadOasisAuthorInfo(const KoXmlNode &metaDoc) { KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, "creator"); if (!e.isNull() && !e.text().isEmpty()) setActiveAuthorInfo("creator", e.text()); KoXmlNode n = metaDoc.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (!n.isElement()) continue; KoXmlElement e = n.toElement(); if (!(e.namespaceURI() == KoXmlNS::meta && e.localName() == "user-defined" && !e.text().isEmpty())) continue; QString name = e.attributeNS(KoXmlNS::meta, "name", QString()); setActiveAuthorInfo(name, e.text()); } return true; } bool KoDocumentInfo::loadAuthorInfo(const KoXmlElement &e) { KoXmlNode n = e.namedItem("author").firstChild(); for (; !n.isNull(); n = n.nextSibling()) { KoXmlElement e = n.toElement(); if (e.isNull()) continue; if (e.tagName() == "full-name") setActiveAuthorInfo("creator", e.text().trimmed()); else setActiveAuthorInfo(e.tagName(), e.text().trimmed()); } return true; } QDomElement KoDocumentInfo::saveAuthorInfo(QDomDocument &doc) { QDomElement e = doc.createElement("author"); QDomElement t; Q_FOREACH (const QString &tag, m_authorTags) { if (tag == "creator") t = doc.createElement("full-name"); else t = doc.createElement(tag); e.appendChild(t); t.appendChild(doc.createTextNode(authorInfo(tag))); } return e; } bool KoDocumentInfo::saveOasisAboutInfo(KoXmlWriter &xmlWriter) { Q_FOREACH (const QString &tag, m_aboutTags) { if (!aboutInfo(tag).isEmpty() || tag == "title") { if (tag == "keyword") { Q_FOREACH (const QString & tmp, aboutInfo("keyword").split(';')) { xmlWriter.startElement("meta:keyword"); xmlWriter.addTextNode(tmp); xmlWriter.endElement(); } } else if (tag == "title" || tag == "description" || tag == "subject" || tag == "date" || tag == "language") { QByteArray elementName(QString("dc:" + tag).toLatin1()); xmlWriter.startElement(elementName.constData()); xmlWriter.addTextNode(aboutInfo(tag)); xmlWriter.endElement(); } else { QByteArray elementName(QString("meta:" + tag).toLatin1()); xmlWriter.startElement(elementName.constData()); xmlWriter.addTextNode(aboutInfo(tag)); xmlWriter.endElement(); } } } return true; } bool KoDocumentInfo::loadOasisAboutInfo(const KoXmlNode &metaDoc) { QStringList keywords; KoXmlElement e; forEachElement(e, metaDoc) { QString tag(e.localName()); if (! m_aboutTags.contains(tag) && tag != "generator") { continue; } //debugOdf<<"localName="< 0) { setAboutInfo("keyword", keywords.join(", ")); } return true; } bool KoDocumentInfo::loadAboutInfo(const KoXmlElement &e) { KoXmlNode n = e.namedItem("about").firstChild(); KoXmlElement tmp; for (; !n.isNull(); n = n.nextSibling()) { tmp = n.toElement(); if (tmp.isNull()) continue; if (tmp.tagName() == "abstract") setAboutInfo("abstract", tmp.text()); setAboutInfo(tmp.tagName(), tmp.text()); } return true; } QDomElement KoDocumentInfo::saveAboutInfo(QDomDocument &doc) { QDomElement e = doc.createElement("about"); QDomElement t; Q_FOREACH (const QString &tag, m_aboutTags) { if (tag == "abstract") { t = doc.createElement("abstract"); e.appendChild(t); t.appendChild(doc.createCDATASection(aboutInfo(tag))); } else { t = doc.createElement(tag); e.appendChild(t); t.appendChild(doc.createTextNode(aboutInfo(tag))); } } return e; } void KoDocumentInfo::updateParametersAndBumpNumCycles() { KoDocumentBase *doc = dynamic_cast< KoDocumentBase *>(parent()); if (doc && doc->isAutosaving()) { return; } setAboutInfo("editing-cycles", QString::number(aboutInfo("editing-cycles").toInt() + 1)); setAboutInfo("date", QDateTime::currentDateTime().toString(Qt::ISODate)); updateParameters(); } void KoDocumentInfo::updateParameters() { KoDocumentBase *doc = dynamic_cast< KoDocumentBase *>(parent()); if (doc && (!doc->isModified())) { return; } KConfig config("kritarc"); config.reparseConfiguration(); KConfigGroup authorGroup(&config, "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); config.reparseConfiguration(); KConfigGroup appAuthorGroup(&config, "Author"); QString profile = appAuthorGroup.readEntry("active-profile", ""); if (profiles.contains(profile)) { KConfigGroup cgs(&authorGroup, "Author-" + profile); setActiveAuthorInfo("creator", cgs.readEntry("creator")); setActiveAuthorInfo("initial", cgs.readEntry("initial")); setActiveAuthorInfo("author-title", cgs.readEntry("author-title")); setActiveAuthorInfo("email", cgs.readEntry("email")); setActiveAuthorInfo("telephone", cgs.readEntry("telephone")); setActiveAuthorInfo("telephone-work", cgs.readEntry("telephone-work")); setActiveAuthorInfo("fax", cgs.readEntry("fax")); setActiveAuthorInfo("country",cgs.readEntry("country")); setActiveAuthorInfo("postal-code",cgs.readEntry("postal-code")); setActiveAuthorInfo("city", cgs.readEntry("city")); setActiveAuthorInfo("street", cgs.readEntry("street")); setActiveAuthorInfo("position", cgs.readEntry("position")); setActiveAuthorInfo("company", cgs.readEntry("company")); } else { if (profile == "anonymous") { setActiveAuthorInfo("creator", QString()); setActiveAuthorInfo("telephone", QString()); setActiveAuthorInfo("telephone-work", QString()); setActiveAuthorInfo("email", QString()); } else { KUser user(KUser::UseRealUserID); setActiveAuthorInfo("creator", user.property(KUser::FullName).toString()); setActiveAuthorInfo("telephone-work", user.property(KUser::WorkPhone).toString()); setActiveAuthorInfo("telephone", user.property(KUser::HomePhone).toString()); KEMailSettings eMailSettings; setActiveAuthorInfo("email", eMailSettings.getSetting(KEMailSettings::EmailAddress)); } setActiveAuthorInfo("initial", ""); setActiveAuthorInfo("author-title", ""); setActiveAuthorInfo("fax", ""); setActiveAuthorInfo("country", ""); setActiveAuthorInfo("postal-code", ""); setActiveAuthorInfo("city", ""); setActiveAuthorInfo("street", ""); setActiveAuthorInfo("position", ""); setActiveAuthorInfo("company", ""); } //alllow author info set programatically to override info from author profile Q_FOREACH (const QString &tag, m_authorTags) { if (m_authorInfoOverride.contains(tag)) { setActiveAuthorInfo(tag, m_authorInfoOverride.value(tag)); } } } void KoDocumentInfo::resetMetaData() { setAboutInfo("editing-cycles", QString::number(0)); setAboutInfo("initial-creator", authorInfo("creator")); setAboutInfo("creation-date", QDateTime::currentDateTime().toString(Qt::ISODate)); setAboutInfo("editing-time", QString::number(0)); } QString KoDocumentInfo::originalGenerator() const { return m_generator; } void KoDocumentInfo::setOriginalGenerator(const QString &generator) { m_generator = generator; } diff --git a/libs/odf/KoDocumentInfo.h b/libs/odf/KoDocumentInfo.h index 49d64b4521..9845a5775a 100644 --- a/libs/odf/KoDocumentInfo.h +++ b/libs/odf/KoDocumentInfo.h @@ -1,213 +1,214 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis Copyright (C) 2004 David Faure Copyright (C) 2006 Martin Pfeiffer 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_DOCUMENT_INFO_H #define KO_DOCUMENT_INFO_H #include #include #include #include #include "kritaodf_export.h" #include class QDomDocument; class QDomElement; class KoStore; class KoXmlWriter; /** * @short The class containing all meta information about a document * * @author Torben Weis * @author David Faure * @author Martin Pfeiffer * @see KoDocumentInfoDlg * * This class contains the meta information for a document. They are * stored in two QMap and can be accessed through aboutInfo() and authorInfo(). * The about info can be changed with setAboutInfo() and setAuthorInfo() */ class KRITAODF_EXPORT KoDocumentInfo : public QObject { Q_OBJECT public: /** * The constructor * @param parent a pointer to the parent object */ explicit KoDocumentInfo(QObject *parent = 0); + explicit KoDocumentInfo(const KoDocumentInfo &rhs, QObject *parent = 0); /** The destructor */ ~KoDocumentInfo() override; /** * Load the KoDocumentInfo from an Calligra-1.3 DomDocument * @param doc the QDomDocument to load from * @return true if success */ bool load(const KoXmlDocument& doc); /** * Save the KoDocumentInfo to an Calligra-1.3 DomDocument * @return the QDomDocument to which was saved */ QDomDocument save(QDomDocument &doc); /** * Set information about the author. * This will override any information retrieved from the author profile * But it does not change the author profile * Note: authorInfo() will not return the new value until the document has been * saved by the user.(autosave doesn't count) * @param info the kind of information to set * @param data the data to set for this information */ void setAuthorInfo(const QString& info, const QString& data); /** * Obtain information about the author * @param info the kind of information to obtain * @return a QString with the information */ QString authorInfo(const QString& info) const; /** * Set information about the document * @param info the kind of information to set * @param data the data to set for this information */ void setAboutInfo(const QString& info, const QString& data); /** * Obtain information about the document * @param info the kind of information to obtain * @return a QString with the information */ QString aboutInfo(const QString& info) const; /** * Obtain the generator of the document, as it was loaded from the document */ QString originalGenerator() const; /** * Sets the original generator of the document. This does not affect what gets * saved to a document in the meta:generator field, it only changes what * originalGenerator() will return. */ void setOriginalGenerator(const QString& generator); /** Resets part of the meta data */ void resetMetaData(); /** Takes care of updating the document info from configuration correctly */ void updateParameters(); private: /// Bumps the editing cycles count and save date, and then calls updateParameters void updateParametersAndBumpNumCycles(); /** * Set information about the author * This sets what is actually saved to file. The public method setAuthorInfo() can be used to set * values that overide what is fetched from the author profile. During saveParameters() author * profile and any overrides is combined resulting in calls to this method. * @param info the kind of information to set * @param data the data to set for this information */ void setActiveAuthorInfo(const QString& info, const QString& data); /** * Load the information about the document from an OASIS file * @param metaDoc a reference to the information node * @return true if success */ bool loadOasisAboutInfo(const KoXmlNode& metaDoc); /** * Save the information about the document to an OASIS file * @param xmlWriter a reference to the KoXmlWriter to write in * @return true if success */ bool saveOasisAboutInfo(KoXmlWriter &xmlWriter); /** * Load the information about the document from a Calligra-1.3 file * @param e the element to load from * @return true if success */ bool loadAboutInfo(const KoXmlElement& e); /** * Save the information about the document to a Calligra-1.3 file * @param doc the QDomDocument to save in * @return the QDomElement to which was saved */ QDomElement saveAboutInfo(QDomDocument& doc); /** * Load the information about the document from an OASIS file * @param metaDoc a reference to the information node * @return true if success */ bool loadOasisAuthorInfo(const KoXmlNode& metaDoc); /** * Load the information about the document from a Calligra-1.3 file * @param e the element to load from * @return true if success */ bool loadAuthorInfo(const KoXmlElement& e); /** * Save the information about the author to a Calligra-1.3 file * @param doc the QDomDocument to save in * @return the QDomElement to which was saved */ QDomElement saveAuthorInfo(QDomDocument& doc); /** * Save the information about the document to an OASIS file * @param xmlWriter a reference to the KoXmlWriter to write in * @return true if success */ bool saveOasisAuthorInfo(KoXmlWriter &xmlWriter); /** A QStringList containing all tags for the document information */ QStringList m_aboutTags; /** A QStringList containing all tags for the author information */ QStringList m_authorTags; /** The map containing information about the author */ QMap m_authorInfo; /** The map containing information about the author set programatically*/ QMap m_authorInfoOverride; /** The map containing information about the document */ QMap m_aboutInfo; /** The original meta:generator of the document */ QString m_generator; Q_SIGNALS: void infoUpdated(const QString &info, const QString &data); }; #endif diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index b239346684..8755687e09 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1559 +1,1567 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 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 // qFromLittleEndian #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColor.h" #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColorModelStandardIds.h" struct KoColorSet::Private { KoColorSet::PaletteType paletteType; QByteArray data; QString comment; qint32 columns; QVector colors; //ungrouped colors QStringList groupNames; //names of the groups, this is used to determine the order they are in. QMap> groups; //grouped colors. }; KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba) { QFileInfo fi(fileName); // .pal if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { return KoColorSet::RIFF_PAL; } // .gpl else if (ba.startsWith("GIMP Palette")) { return KoColorSet::GPL; } // .pal else if (ba.startsWith("JASC-PAL")) { return KoColorSet::PSP_PAL; } else if (fi.suffix().toLower() == "aco") { return KoColorSet::ACO; } else if (fi.suffix().toLower() == "act") { return KoColorSet::ACT; } else if (fi.suffix().toLower() == "xml") { return KoColorSet::XML; } else if (fi.suffix().toLower() == "kpl") { return KoColorSet::KPL; } else if (fi.suffix().toLower() == "sbz") { return KoColorSet::SBZ; } return KoColorSet::UNKNOWN; } KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private()) { // Implemented in KoResource class d->columns = 0; // Set the default value that the GIMP uses... } KoColorSet::KoColorSet() : KoResource(QString()) , d(new Private()) { d->columns = 0; // Set the default value that the GIMP uses... } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); d->comment = rhs.d->comment; d->columns = rhs.d->columns; d->colors = rhs.d->colors; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; setValid(true); } KoColorSet::~KoColorSet() { } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return init(); } bool KoColorSet::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: res = saveGpl(dev); break; default: res = saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } bool KoColorSet::init() { d->colors.clear(); // just in case this is a reload (eg by KoEditColorSetDialog), d->groups.clear(); d->groupNames.clear(); if (filename().isNull()) { warnPigment << "Cannot load palette" << name() << "there is no filename set"; return false; } if (d->data.isNull()) { QFile file(filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); d->data = file.readAll(); file.close(); } bool res = false; d->paletteType = detectFormat(filename(), d->data); switch(d->paletteType) { case GPL: res = loadGpl(); break; case ACT: res = loadAct(); break; case RIFF_PAL: res = loadRiff(); break; case PSP_PAL: res = loadPsp(); break; case ACO: res = loadAco(); break; case XML: res = loadXml(); break; case KPL: res = loadKpl(); break; case SBZ: res = loadSbz(); break; default: res = false; } setValid(res); if (d->columns == 0) { d->columns = 10; } QImage img(d->columns * 4, (d->colors.size() / d->columns) * 4, QImage::Format_ARGB32); QPainter gc(&img); gc.fillRect(img.rect(), Qt::darkGray); int counter = 0; for(int i = 0; i < d->columns; ++i) { for (int j = 0; j < (d->colors.size() / d->columns); ++j) { if (counter < d->colors.size()) { QColor c = d->colors.at(counter).color.toQColor(); gc.fillRect(i * 4, j * 4, 4, 4, c); counter++; } else { break; } } } setImage(img); // save some memory d->data.clear(); return res; } bool KoColorSet::saveGpl(QIODevice *dev) const { QTextStream stream(dev); stream << "GIMP Palette\nName: " << name() << "\nColumns: " << d->columns << "\n#\n"; for (int i = 0; i < d->colors.size(); i++) { const KoColorSetEntry& entry = d->colors.at(i); QColor c = entry.color.toQColor(); stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; if (entry.name.isEmpty()) stream << "Untitled\n"; else stream << entry.name << "\n"; } return true; } quint32 KoColorSet::nColors() { quint32 total = d->colors.count(); if (!d->groups.empty()) { Q_FOREACH (const QVector &group, d->groups.values()) { total += group.size(); } } return total; } quint32 KoColorSet::nColorsGroup(QString groupName) { if (d->groups.contains(groupName)) { return d->groups.value(groupName).size(); } else if (groupName.isEmpty()){ return d->colors.size(); } else { return 0; } } quint32 KoColorSet::getIndexClosestColor(const KoColor color, bool useGivenColorSpace) { quint32 closestIndex = 0; quint8 highestPercentage = 0; quint8 testPercentage = 0; KoColor compare = color; for (quint32 i=0; idifference(compare.data(), entry.data())); if (testPercentage>highestPercentage) { closestIndex = i; highestPercentage = testPercentage; } } return closestIndex; } QString KoColorSet::closestColorName(const KoColor color, bool useGivenColorSpace) { int i = getIndexClosestColor(color, useGivenColorSpace); QString name = getColorGlobal(i).name; return name; } void KoColorSet::add(const KoColorSetEntry & c, QString groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { d->groups[groupName].push_back(c); } else { d->colors.push_back(c); } } quint32 KoColorSet::insertBefore(const KoColorSetEntry &c, qint32 index, const QString &groupName) { quint32 newIndex = index; if (d->groups.contains(groupName)) { d->groups[groupName].insert(index, c); } else if (groupName.isEmpty()){ d->colors.insert(index, c);; } else { warnPigment << "Couldn't find group to insert to"; } return newIndex; } void KoColorSet::removeAt(quint32 index, QString groupName) { if (d->groups.contains(groupName)){ if ((quint32)d->groups.value(groupName).size()>index) { d->groups[groupName].remove(index); } } else { if ((quint32)d->colors.size()>index) { d->colors.remove(index); } } } void KoColorSet::clear() { d->colors.clear(); d->groups.clear(); } KoColorSetEntry KoColorSet::getColorGlobal(quint32 index) { KoColorSetEntry e; quint32 groupIndex = index; QString groupName = findGroupByGlobalIndex(index, &groupIndex); e = getColorGroup(groupIndex, groupName); return e; } KoColorSetEntry KoColorSet::getColorGroup(quint32 index, QString groupName) { KoColorSetEntry e; - if (d->groups.contains(groupName) && index<(quint32)d->groups.value(groupName).size()) { - e = d->groups.value(groupName).at(index); - } else if (groupName == QString() && index<(quint32)d->colors.size()) { - e = d->colors.at(index); + if (d->groups.contains(groupName)) { + if (nColorsGroup(groupName)>index) { + e = d->groups.value(groupName).at(index); + } else { + warnPigment<index) { + e = d->colors.at(index); + } else { + warnPigment<colors.size()<=*index) { *index -= (quint32)d->colors.size(); if (!d->groups.empty() || !d->groupNames.empty()) { QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { quint32 size = (quint32)d->groups.value(name).size(); if (size<=*index) { *index -= size; } else { groupName = name; return groupName; } } } } return groupName; } QString KoColorSet::findGroupByColorName(const QString &name, quint32 *index) { *index = 0; QString groupName = QString(); for (int i = 0; icolors.size(); i++) { if(d->colors.at(i).name == name) { *index = (quint32)i; return groupName; } } QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { for (int i=0; igroups[name].size(); i++) { if(d->groups[name].at(i).name == name) { *index = (quint32)i; groupName = name; return groupName; } } } return groupName; } QString KoColorSet::findGroupByID(const QString &id, quint32 *index) { *index = 0; QString groupName = QString(); for (int i = 0; icolors.size(); i++) { if(d->colors.at(i).id == id) { *index = (quint32)i; return groupName; } } QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { for (int i=0; igroups[name].size(); i++) { if(d->groups[name].at(i).id == id) { *index = (quint32)i; groupName = name; return groupName; } } } return groupName; } QStringList KoColorSet::getGroupNames() { if (d->groupNames.size()groups.size()) { warnPigment << "mismatch between groups and the groupnames list."; return QStringList(d->groups.keys()); } return d->groupNames; } bool KoColorSet::changeGroupName(QString oldGroupName, QString newGroupName) { if (d->groupNames.contains(oldGroupName)==false) { return false; } QVector dummyList = d->groups.value(oldGroupName); d->groups.remove(oldGroupName); d->groups[newGroupName] = dummyList; //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } bool KoColorSet::changeColorSetEntry(KoColorSetEntry entry, QString groupName, quint32 index) { if (index>=nColorsGroup(groupName) || (d->groupNames.contains(groupName)==false && groupName.size()>0)) { return false; } if (groupName==QString()) { d->colors[index] = entry; } else { d->groups[groupName][index] = entry; } return true; } void KoColorSet::setColumnCount(int columns) { d->columns = columns; } int KoColorSet::columnCount() { return d->columns; } QString KoColorSet::comment() { return d->comment; } void KoColorSet::setComment(QString comment) { d->comment = comment; } bool KoColorSet::addGroup(const QString &groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { return false; } d->groupNames.append(groupName); d->groups[groupName]; return true; } bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { if (d->groupNames.contains(groupName)==false || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } d->groupNames.removeAt(d->groupNames.indexOf(groupName)); int index = d->groupNames.size(); if (groupNameInsertBefore!=QString()) { index = d->groupNames.indexOf(groupNameInsertBefore); } d->groupNames.insert(index, groupName); return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } if (keepColors) { for (int i = 0; igroups.value(groupName).size(); i++) { d->colors.append(d->groups.value(groupName).at(i)); } } for(int n = 0; ngroupNames.size(); n++) { if (d->groupNames.at(n) == groupName) { d->groupNames.removeAt(n); } } d->groups.remove(groupName); return true; } QString KoColorSet::defaultFileExtension() const { return QString(".kpl"); } bool KoColorSet::loadGpl() { QString s = QString::fromUtf8(d->data.data(), d->data.count()); if (s.isEmpty() || s.isNull() || s.length() < 50) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } quint32 index = 0; QStringList lines = s.split('\n', QString::SkipEmptyParts); if (lines.size() < 3) { return false; } QString columns; qint32 r, g, b; KoColorSetEntry e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].startsWith("Name: ")) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } setName(i18n(lines[1].mid(strlen("Name: ")).trimmed().toLatin1())); index = 2; // Read columns if (lines[index].startsWith("Columns: ")) { columns = lines[index].mid(strlen("Columns: ")).trimmed(); d->columns = columns.toInt(); index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { d->comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { break; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); QString name = a.join(" "); e.name = name.isEmpty() ? i18n("Untitled") : name; add(e); } } return true; } bool KoColorSet::loadAct() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; for (int i = 0; i < d->data.size(); i += 3) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); add(e); } return true; } struct RiffHeader { quint32 riff; quint32 size; quint32 signature; quint32 data; quint32 datasize; quint16 version; quint16 colorcount; }; bool KoColorSet::loadRiff() { // http://worms2d.info/Palette_file QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; RiffHeader header; memcpy(&header, d->data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < d->data.size()); i += 4) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); add(e); } return true; } bool KoColorSet::loadPsp() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; qint32 r, g, b; QString s = QString::fromUtf8(d->data.data(), d->data.count()); QStringList l = s.split('\n', QString::SkipEmptyParts); if (l.size() < 4) return false; if (l[0] != "JASC-PAL") return false; if (l[1] != "0100") return false; int entries = l[2].toInt(); for (int i = 0; i < entries; ++i) { QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() != 3) { continue; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); QString name = a.join(" "); e.name = name.isEmpty() ? i18n("Untitled") : name; add(e); } return true; } void scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { KoColorSetEntry currentColor; //It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); QStringRef colorValue; // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); QStringRef colorName = colorProperties.value("NAME"); currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); currentColor.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); colorValue = colorProperties.value("RGB"); if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid rgb8 color (malformed): " + colorValue); return; } else { bool rgbOk; quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16); if (!rgbOk) { xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue); return; } quint8 r = rgb >> 16 & 0xff; quint8 g = rgb >> 8 & 0xff; quint8 b = rgb & 0xff; dbgPigment << "Color parsed: "<< r << g << b; currentColor.color.data()[0] = r; currentColor.color.data()[1] = g; currentColor.color.data()[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_U8); set->add(currentColor); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else if (colorProperties.hasAttribute("CMYK")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK"); QStringRef colorName = colorProperties.value("NAME"); currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid cmyk color (malformed): " % colorValue); return; } else { bool cmykOk; quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits if (!cmykOk) { xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue); return; } quint8 c = cmyk >> 24 & 0xff; quint8 m = cmyk >> 16 & 0xff; quint8 y = cmyk >> 8 & 0xff; quint8 k = cmyk & 0xff; dbgPigment << "Color parsed: "<< c << m << y << k; currentColor.color.data()[0] = c; currentColor.color.data()[1] = m; currentColor.color.data()[2] = y; currentColor.color.data()[3] = k; currentColor.color.setOpacity(OPACITY_OPAQUE_U8); set->add(currentColor); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { xml->raiseError("Unknown color space for color " + currentColor.name); } } bool loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) { //1. Get name QXmlStreamAttributes paletteProperties = xml->attributes(); QStringRef paletteName = paletteProperties.value("Name"); dbgPigment << "Processed name of palette:" << paletteName; set->setName(paletteName.toString()); //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them while(xml->readNextStartElement()) { QStringRef currentElement = xml->name(); if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { scribusParseColor(set, xml); } else { xml->skipCurrentElement(); } } if(xml->hasError()) { return false; } return true; } bool KoColorSet::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(d->data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << filename() << ", Scribus format"; res = loadScribusXmlPalette(this, xml); } else { // Unknown XML format xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); } } // If there is any error (it should be returned through the stream) if (xml->hasError() || !res) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << filename(); return true; } } bool KoColorSet::saveKpl(QIODevice *dev) const { QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-palette", KoStore::Zip)); if (!store || store->bad()) return false; QSet profiles; QMap profileMap; { QDomDocument doc; QDomElement root = doc.createElement("Colorset"); root.setAttribute("version", "1.0"); root.setAttribute("name", name()); root.setAttribute("comment", d->comment); root.setAttribute("columns", d->columns); Q_FOREACH(const KoColorSetEntry &entry, d->colors) { // Only save non-builtin profiles.= const KoColorProfile *profile = entry.color.colorSpace()->profile(); if (!profile->fileName().isEmpty()) { profiles << profile; profileMap[profile] = entry.color.colorSpace(); } QDomElement el = doc.createElement("ColorSetEntry"); el.setAttribute("name", entry.name); el.setAttribute("id", entry.id); el.setAttribute("spot", entry.spotColor ? "true" : "false"); el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); entry.color.toXML(doc, el); root.appendChild(el); } Q_FOREACH(const QString &groupName, d->groupNames) { QDomElement gl = doc.createElement("Group"); gl.setAttribute("name", groupName); root.appendChild(gl); Q_FOREACH(const KoColorSetEntry &entry, d->groups.value(groupName)) { // Only save non-builtin profiles.= const KoColorProfile *profile = entry.color.colorSpace()->profile(); if (!profile->fileName().isEmpty()) { profiles << profile; profileMap[profile] = entry.color.colorSpace(); } QDomElement el = doc.createElement("ColorSetEntry"); el.setAttribute("name", entry.name); el.setAttribute("id", entry.id); el.setAttribute("spot", entry.spotColor ? "true" : "false"); el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); entry.color.toXML(doc, el); gl.appendChild(el); } } doc.appendChild(root); if (!store->open("colorset.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } } QDomDocument doc; QDomElement profileElement = doc.createElement("Profiles"); Q_FOREACH(const KoColorProfile *profile, profiles) { QString fn = QFileInfo(profile->fileName()).fileName(); if (!store->open(fn)) { return false; } QByteArray profileRawData = profile->rawData(); if (!store->write(profileRawData)) { return false; } if (!store->close()) { return false; } QDomElement el = doc.createElement("Profile"); el.setAttribute("filename", fn); el.setAttribute("name", profile->name()); el.setAttribute("colorModelId", profileMap[profile]->colorModelId().id()); el.setAttribute("colorDepthId", profileMap[profile]->colorDepthId().id()); profileElement.appendChild(el); } doc.appendChild(profileElement); if (!store->open("profiles.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } return store->finalize(); } bool KoColorSet::loadKpl() { QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-palette", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("profiles.xml")) { if (!store->open("profiles.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement("Profiles"); while (!c.isNull()) { QString name = c.attribute("name"); QString filename = c.attribute("filename"); QString colorModelId = c.attribute("colorModelId"); QString colorDepthId = c.attribute("colorDepthId"); if (!KoColorSpaceRegistry::instance()->profileByName(name)) { store->open(filename); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); } } c = c.nextSiblingElement(); } } { if (!store->open("colorset.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); setName(e.attribute("name")); d->comment = e.attribute("comment"); d->columns = e.attribute("columns").toInt(); QDomElement c = e.firstChildElement("ColorSetEntry"); while (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColorSetEntry entry; entry.color = KoColor::fromXML(c.firstChildElement(), colorDepthId); entry.name = c.attribute("name"); entry.id = c.attribute("id"); entry.spotColor = c.attribute("spot", "false") == "true" ? true : false; d->colors << entry; c = c.nextSiblingElement("ColorSetEntry"); } QDomElement g = e.firstChildElement("Group"); while (!g.isNull()) { QString groupName = g.attribute("name"); addGroup(groupName); QDomElement cg = g.firstChildElement("ColorSetEntry"); while (!cg.isNull()) { QString colorDepthId = cg.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColorSetEntry entry; entry.color = KoColor::fromXML(cg.firstChildElement(), colorDepthId); entry.name = cg.attribute("name"); entry.id = cg.attribute("id"); entry.spotColor = cg.attribute("spot", "false") == "true" ? true : false; add(entry, groupName); cg = cg.nextSiblingElement("ColorSetEntry"); } g = g.nextSiblingElement("Group"); } } buf.close(); return true; } quint16 readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::loadAco() { QFileInfo info(filename()); setName(info.baseName()); QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KoColorSetEntry e; if (version == 1 && buf.size() > 4+numColors*10) { buf.seek(4+numColors*10); version = readShort(&buf); numColors = readShort(&buf); } const quint16 quint16_MAX = 65535; for (int i = 0; i < numColors && !buf.atEnd(); ++i) { quint16 colorSpace = readShort(&buf); quint16 ch1 = readShort(&buf); quint16 ch2 = readShort(&buf); quint16 ch3 = readShort(&buf); quint16 ch4 = readShort(&buf); bool skip = false; if (colorSpace == 0) { // RGB const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16(srgb)); reinterpret_cast(e.color.data())[0] = ch3; reinterpret_cast(e.color.data())[1] = ch2; reinterpret_cast(e.color.data())[2] = ch1; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 1) { // HSB e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16()); QColor c; c.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); e.color.fromQColor(c); e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 2) { // CMYK e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(e.color.data())[0] = quint16_MAX - ch1; reinterpret_cast(e.color.data())[1] = quint16_MAX - ch2; reinterpret_cast(e.color.data())[2] = quint16_MAX - ch3; reinterpret_cast(e.color.data())[3] = quint16_MAX - ch4; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 7) { // LAB e.color = KoColor(KoColorSpaceRegistry::instance()->lab16()); reinterpret_cast(e.color.data())[0] = ch3; reinterpret_cast(e.color.data())[1] = ch2; reinterpret_cast(e.color.data())[2] = ch1; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 8) { // GRAY e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(e.color.data())[0] = ch1 * (quint16_MAX / 10000); e.color.setOpacity(OPACITY_OPAQUE_U8); } else { warnPigment << "Unsupported colorspace in palette" << filename() << "(" << colorSpace << ")"; skip = true; } if (version == 2) { quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped. Q_UNUSED(v2); quint16 size = readShort(&buf) -1; //then comes the length if (size>0) { QByteArray ba = buf.read(size*2); if (ba.size() == size*2) { QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE"); e.name = Utf16Codec->toUnicode(ba); } else { warnPigment << "Version 2 name block is the wrong size" << filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { add(e); } } return true; } bool KoColorSet::loadSbz() { QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); // &buf is a subclass of QIODevice QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("swatchbook.xml")) { // Try opening... if (!store->open("swatchbook.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); dbgPigment << "XML palette: " << filename() << ", SwatchBooker format"; QDomDocument doc; int errorLine, errorColumn; QString errorMessage; bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); if (!status) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage; return false; } QDomElement e = doc.documentElement(); // SwatchBook // Start reading properties... QDomElement metadata = e.firstChildElement("metadata"); if (e.isNull()) { warnPigment << "Palette metadata not found"; return false; } QDomElement title = metadata.firstChildElement("dc:title"); QString colorName = title.text(); colorName = colorName.isEmpty() ? i18n("Untitled") : colorName; setName(colorName); dbgPigment << "Processed name of palette:" << name(); // End reading properties // Now read colors... QDomElement materials = e.firstChildElement("materials"); if (materials.isNull()) { warnPigment << "Materials (color definitions) not found"; return false; } // This one has lots of "color" elements QDomElement colorElement = materials.firstChildElement("color"); if (colorElement.isNull()) { warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")"; return false; } // Also read the swatch book... QDomElement book = e.firstChildElement("book"); if (book.isNull()) { warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")"; return false; } // Which has lots of "swatch"es (todo: support groups) QDomElement swatch = book.firstChildElement(); if (swatch.isNull()) { warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")"; return false; } // We'll store colors here, and as we process swatches // we'll add them to the palette QHash materialsBook; QHash fileColorSpaces; // Color processing for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) { KoColorSetEntry currentColor; // Set if color is spot currentColor.spotColor = colorElement.attribute("usage") == "spot"; // inside contains id and name // one or more define the color QDomElement currentColorMetadata = colorElement.firstChildElement("metadata"); QDomNodeList currentColorValues = colorElement.elementsByTagName("values"); // Get color name QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title"); QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier"); // Is there an id? (we need that at the very least for identifying a color) if (colorId.text().isEmpty()) { warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } if (materialsBook.contains(colorId.text())) { warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } // Get a valid color name currentColor.id = colorId.text(); currentColor.name = colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text(); // Get a valid color definition if (currentColorValues.isEmpty()) { warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")"; return false; } bool firstDefinition = false; const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); // Priority: Lab, otherwise the first definition found for(int j = 0; j < currentColorValues.size(); j++) { QDomNode colorValue = currentColorValues.at(j); QDomElement colorValueE = colorValue.toElement(); QString model = colorValueE.attribute("model", QString()); // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5 // Lab: L 0 -> 100 : ab -128 -> 127 // XYZ: 0 -> ~100 if (model == "Lab") { QStringList lab = colorValueE.text().split(" "); if (lab.length() != 3) { warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float l = lab.at(0).toFloat(&status); float a = lab.at(1).toFloat(&status); float b = lab.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(currentColor.color.data())[0] = l; reinterpret_cast(currentColor.color.data())[1] = a; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; break; // Immediately add this one } else if (model == "sRGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); reinterpret_cast(currentColor.color.data())[0] = r; reinterpret_cast(currentColor.color.data())[1] = g; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "XYZ" && !firstDefinition) { QStringList xyz = colorValueE.text().split(" "); if (xyz.length() != 3) { warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float x = xyz.at(0).toFloat(&status); float y = xyz.at(1).toFloat(&status); float z = xyz.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(currentColor.color.data())[0] = x; reinterpret_cast(currentColor.color.data())[1] = y; reinterpret_cast(currentColor.color.data())[2] = z; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } // The following color spaces admit an ICC profile (in SwatchBooker) else if (model == "CMYK" && !firstDefinition) { QStringList cmyk = colorValueE.text().split(" "); if (cmyk.length() != 4) { warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float c = cmyk.at(0).toFloat(&status); float m = cmyk.at(1).toFloat(&status); float y = cmyk.at(2).toFloat(&status); float k = cmyk.at(3).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = c; reinterpret_cast(currentColor.color.data())[1] = m; reinterpret_cast(currentColor.color.data())[2] = y; reinterpret_cast(currentColor.color.data())[3] = k; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "GRAY" && !firstDefinition) { QString gray = colorValueE.text(); float g = gray.toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = g; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "RGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = r; reinterpret_cast(currentColor.color.data())[1] = g; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else { warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; } } if (firstDefinition) { materialsBook.insert(currentColor.id, currentColor); } else { warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")"; return false; } } // End colors // Now decide which ones will go into the palette for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) { QString type = swatch.tagName(); if (type.isEmpty() || type.isNull()) { warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } else if (type == "swatch") { QString id = swatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { add(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } } else if (type == "group") { QDomElement groupMetadata = swatch.firstChildElement("metadata"); if (groupMetadata.isNull()) { warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")"; return false; } QDomElement groupTitle = metadata.firstChildElement("dc:title"); if (groupTitle.isNull()) { warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")"; return false; } QString currentGroupName = groupTitle.text(); QDomElement groupSwatch = swatch.firstChildElement("swatch"); while(!groupSwatch.isNull()) { QString id = groupSwatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { add(materialsBook.value(id), currentGroupName); } else { warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } groupSwatch = groupSwatch.nextSiblingElement("swatch"); } } } // End palette } buf.close(); return true; } diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index dee0b53228..f24b3547e2 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,554 +1,558 @@ 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_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_paintop_presets_save.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 input/KisQtWidgetsTweaker.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp + KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoStackAction.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisPaletteModel.cpp kis_palette_delegate.cpp kis_palette_view.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp ) if(WIN32) if (NOT Qt5Gui_PRIVATE_INCLUDE_DIRS) message(FATAL_ERROR "Qt5Gui Private header are missing!") endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp ) 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/wdgsavebrushpreset.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui brushhud/kis_dlg_brush_hud_config.ui forms/wdgdlginternalcolorselector.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) 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 4ba0aa38ec..d9de4c0c80 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,777 +1,769 @@ /* 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))) { + if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { 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)) { + KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + if (m_mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { 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); + KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } 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); + KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } 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); + QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection - if (m_autosaveFiles.size() > 0) { + if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } - m_autosaveDialog = new KisAutoSaveRecoveryDialog(m_autosaveFiles, activeWindow()); + m_autosaveDialog = new KisAutoSaveRecoveryDialog(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) { + Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } - m_autosaveFiles = filesToRecover; + autosaveFiles = filesToRecover; } else { - m_autosaveFiles.clear(); + autosaveFiles.clear(); } - if (m_autosaveFiles.size() > 0) { + if (autosaveFiles.size() > 0) { QList autosaveUrls; - Q_FOREACH (const QString &autoSaveFile, m_autosaveFiles) { + Q_FOREACH (const QString &autoSaveFile, 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); + KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + m_mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // 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(); + KisMainWindow::OpenFlags batchFlags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { 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/KisApplication.h b/libs/ui/KisApplication.h index bd90c387f3..c3dfac8a87 100644 --- a/libs/ui/KisApplication.h +++ b/libs/ui/KisApplication.h @@ -1,124 +1,123 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_APPLICATION_H #define KIS_APPLICATION_H #include #include #include "kritaui_export.h" #include class KisMainWindow; class KisApplicationPrivate; class QWidget; class KisApplicationArguments; class KisAutoSaveRecoveryDialog; #include /** * @brief Base class for the %Krita app * * This class handles arguments given on the command line and * shows a generic about dialog for the Krita app. * * In addition it adds the standard directories where Krita * can find its images etc. * * If the last mainwindow becomes closed, KisApplication automatically * calls QApplication::quit. */ class KRITAUI_EXPORT KisApplication : public QtSingleApplication { Q_OBJECT public: /** * Creates an application object, adds some standard directories and * initializes kimgio. */ explicit KisApplication(const QString &key, int &argc, char **argv); /** * Destructor. */ ~KisApplication() override; /** * Call this to start the application. * * Parses command line arguments and creates the initial main windowss and docs * from them (or an empty doc if no cmd-line argument is specified ). * * You must call this method directly before calling QApplication::exec. * * It is valid behaviour not to call this method at all. In this case you * have to process your command line parameters by yourself. */ virtual bool start(const KisApplicationArguments &args); /** * Checks if user is holding ctrl+alt+shift keys and asks if the settings file should be cleared. * * Typically called during startup before reading the config. */ void askClearConfig(); /** * Tell KisApplication to show this splashscreen when you call start(); * when start returns, the splashscreen is hidden. Use KSplashScreen * to have the splash show correctly on Xinerama displays. */ void setSplashScreen(QWidget *splash); void setSplashScreenLoadingText(QString); void hideSplashScreen(); /// Overridden to handle exceptions from event handlers. bool notify(QObject *receiver, QEvent *event) override; void addResourceTypes(); void loadResources(); void loadPlugins(); void initializeGlobals(const KisApplicationArguments &args); public Q_SLOTS: void remoteArguments(QByteArray message, QObject*socket); void fileOpenRequested(const QString & url); private: /// @return the number of autosavefiles opened void checkAutosaveFiles(); bool createNewDocFromTemplate(const QString &fileName, KisMainWindow *m_mainWindow); void clearConfig(); private: KisApplicationPrivate * const d; class ResetStarting; friend class ResetStarting; KisAutoSaveRecoveryDialog *m_autosaveDialog; - QStringList m_autosaveFiles; QPointer m_mainWindow; // The first mainwindow we create on startup bool m_batchRun; }; #endif diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 28ac7e70ee..07b9fa0865 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1769 +1,1650 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include +#include "kis_config_notifier.h" +#include "kis_async_action_feedback.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; +namespace { +constexpr int errorMessageTimeout = 5000; +constexpr int successMessageTimeout = 1000; +} + + /********************************************************** * * KisDocument * **********************************************************/ -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) + : KUndo2Stack(doc), + m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: - Private() : - 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), + Private(KisDocument *q) : + docInfo(new KoDocumentInfo(q)), // deleted by QObject + importExportManager(new KisImportExportManager(q)), // deleted manually + undoStack(new UndoStack(q)), // deleted by QObject m_bAutoDetectedMime(false), modified(false), readwrite(true), - disregardAutosaveFailure(false), - nserver(0), - macroNestDepth(0), + firstMod(QDateTime::currentDateTime()), + lastMod(firstMod), + nserver(new KisNameServer(1)), imageIdleWatcher(2000 /*ms*/), - suppressProgress(false), - fileProgressProxy(0), savingLock(&savingMutex) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } + Private(const Private &rhs, KisDocument *q) : + docInfo(new KoDocumentInfo(*rhs.docInfo, q)), + unit(rhs.unit), + importExportManager(new KisImportExportManager(q)), + mimeType(rhs.mimeType), + outputMimeType(rhs.outputMimeType), + undoStack(new UndoStack(q)), + guidesConfig(rhs.guidesConfig), + m_bAutoDetectedMime(rhs.m_bAutoDetectedMime), + m_url(rhs.m_url), + m_file(rhs.m_file), + modified(rhs.modified), + readwrite(rhs.readwrite), + firstMod(rhs.firstMod), + lastMod(rhs.lastMod), + nserver(new KisNameServer(*rhs.nserver)), + preActivatedNode(0), // the node is from another hierarchy! + imageIdleWatcher(2000 /*ms*/), + assistants(rhs.assistants), // WARNING: assistants should not store pointers to the document! + gridConfig(rhs.gridConfig), + savingLock(&savingMutex) + { + } + ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } - KoDocumentInfo *docInfo; - - KoProgressUpdater *progressUpdater; - KoProgressProxy *progressProxy; + KoDocumentInfo *docInfo = 0; KoUnit unit; - KisImportExportManager *importExportManager; // The filter-manager to use when loading/saving [for the options] + KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving - 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 + int autoSaveDelay = 300; // in seconds, 0 to disable. + bool modifiedAfterAutosave = false; + bool isAutosaving = false; + bool disregardAutosaveFailure = false; - KUndo2Stack *undoStack; + KUndo2Stack *undoStack = 0; 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 + bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. - QEventLoop m_eventLoop; + QMutex savingMutex; - bool modified; - bool readwrite; + bool modified = false; + bool readwrite = false; QDateTime firstMod; QDateTime lastMod; - bool disregardAutosaveFailure; - KisNameServer *nserver; - qint32 macroNestDepth; KisImageSP image; KisImageSP savingImage; - KisNodeSP preActivatedNode; - KisShapeController* shapeController; - KoShapeController* koShapeController; + KisNodeWSP preActivatedNode; + KisShapeController* shapeController = 0; + KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; - bool suppressProgress; - KoProgressProxy* fileProgressProxy; - QList assistants; KisGridConfig gridConfig; StdLockableWrapper savingLock; + QScopedPointer backgroundSaveDocument; + QPointer savingUpdater; + QFuture childSavingFuture; + KritaUtils::ExportFileJob backgroundSaveJob; + + bool isRecovered = false; + void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } - class SafeSavingLocker; + class StrippedSafeSavingLocker; }; -class KisDocument::Private::SafeSavingLocker { +class KisDocument::Private::StrippedSafeSavingLocker { 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 + StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) + : m_locked(false) + , m_image(image) + , m_savingLock(savingMutex) + , m_imageLock(image, true) + { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ - m_locked = std::try_lock(m_imageLock, d->savingLock) < 0; + m_locked = std::try_lock(m_imageLock, m_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; - } - } + m_image->requestStrokeEnd(); + QApplication::processEvents(); - if (m_locked) { - d->disregardAutosaveFailure = false; + // one more try... + m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } - ~SafeSavingLocker() { + ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); - d->savingLock.unlock(); - - const int realAutoSaveInterval = KisConfig().autoSaveInterval(); - m_document->setAutoSaveDelay(realAutoSaveInterval); + m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: - KisDocument::Private *d; - KisDocument *m_document; bool m_locked; - + KisImageSP m_image; + StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() - : d(new Private()) + : d(new Private(this)) { - d->undoStack = new UndoStack(this); - d->undoStack->setParent(this); - - d->importExportManager = new KisImportExportManager(this); - d->importExportManager->setProgresUpdater(d->progressUpdater); - + connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); + connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); - KisConfig cfg; - setAutoSaveDelay(cfg.autoSaveInterval()); - setObjectName(newObjectName()); - d->docInfo = new KoDocumentInfo(this); + // preload the krita resources + KisResourceServerProvider::instance(); - d->firstMod = QDateTime::currentDateTime(); - d->lastMod = QDateTime::currentDateTime(); + d->shapeController = new KisShapeController(this, d->nserver), + d->koShapeController = new KoShapeController(0, d->shapeController), + slotConfigChanged(); +} - // preload the krita resources - KisResourceServerProvider::instance(); +KisDocument::KisDocument(const KisDocument &rhs) + : QObject(), + d(new Private(*rhs.d, this)) +{ + connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); + connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); + connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); + setObjectName(rhs.objectName()); - d->nserver = new KisNameServer(1); + d->shapeController = new KisShapeController(this, d->nserver), + d->koShapeController = new KoShapeController(0, d->shapeController), - d->shapeController = new KisShapeController(this, d->nserver); - d->koShapeController = new KoShapeController(0, d->shapeController); + slotConfigChanged(); - undoStack()->setUndoLimit(KisConfig().undoStackLimit()); - connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int))); - setBackupFile(KisConfig().backupFile()); + // clone the image with keeping the GUIDs of the layers intact + // NOTE: we expect the image to be locked! + setCurrentImage(rhs.image()->clone(true)); + + if (rhs.d->preActivatedNode) { + // since we clone uuid's, we can use them for lacating new + // nodes. Otherwise we would need to use findSymmetricClone() + d->preActivatedNode = + KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); + } } KisDocument::~KisDocument() { + // wait until all the pending operations are in progress + waitForSavingToComplete(); + /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer.disconnect(this); d->autoSaveTimer.stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } -bool KisDocument::exportDocument(const QUrl &_url, KisPropertiesConfigurationSP exportConfiguration) +KisDocument *KisDocument::clone() { - //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; + return new KisDocument(*this); } -bool KisDocument::saveAs(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration) +bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { - //qDebug() << "saveAs" << url; - if (!url.isValid() || !url.isLocalFile()) { - errKrita << "saveAs: Malformed URL " << url.url() << endl; + QFileInfo filePathInfo(job.filePath); + + if (filePathInfo.exists() && !filePathInfo.isWritable()) { + slotCompleteSavingDocument(job, + KisImportExportFilter::CreationError, + i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } - 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 + KisConfig cfg; + if (cfg.backupFile() && filePathInfo.exists()) { + KBackup::backupFile(job.filePath); + } + + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); + + const QString actionName = + job.flags & KritaUtils::SaveIsExporting ? + i18n("Exporting Document...") : + i18n("Saving Document..."); + + bool started = + initiateSavingInBackground(actionName, + this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)), + job, exportConfiguration); - if (!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(); + if (!started) { + emit canceled(QString()); } - return result; + return started; } -bool KisDocument::save(KisPropertiesConfigurationSP exportConfiguration) +bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { - //qDebug() << "save" << d->m_file << d->m_url << url() << localFilePath(); - - d->m_saveOk = false; + using namespace KritaUtils; - if (d->m_file.isEmpty()) { // document was created empty - d->m_file = d->m_url.toLocalFile(); + SaveFlags flags = SaveIsExporting; + if (showWarnings) { + flags |= SaveShowWarnings; } - updateEditingTime(true); + return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), + mimeType, + flags), + exportConfiguration); - setFileProgressProxy(); - setUrl(url()); +} - bool ok = saveFile(localFilePath(), exportConfiguration); +bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +{ + using namespace KritaUtils; - clearFileProgressProxy(); + return exportDocumentImpl(ExportFileJob(url.toLocalFile(), + mimeType, + showWarnings ? SaveShowWarnings : SaveNone), + exportConfiguration); +} - 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; +bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +{ + return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); - if (!prepareLocksForSaving()) { + Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); + if (!locker.successfullyLocked()) { return byteArray; } + d->savingImage = d->image; + if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } - unlockAfterSaving(); - return byteArray; } -bool KisDocument::isInSaving() const +void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { - std::unique_lock> l(d->savingLock, std::try_to_lock); - return !l.owns_lock(); + const QString fileName = QFileInfo(job.filePath).fileName(); + + if (status != KisImportExportFilter::OK) { + emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error mesage", + "Error during saving %1: %2", + fileName, + exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); + + if (!fileBatchMode()) { + const QString filePath = job.filePath; + QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage)); + } + } else { + if (!(job.flags & KritaUtils::SaveIsExporting)) { + setUrl(QUrl::fromLocalFile(job.filePath)); + setLocalFilePath(job.filePath); + setMimeType(job.mimeType); + updateEditingTime(true); + + d->undoStack->setClean(); + setRecovered(false); + removeAutoSaveFiles(); + } + + emit completed(); + emit sigSavingFinished(); + + emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); + } } -bool KisDocument::saveFile(const QString &filePath, KisPropertiesConfigurationSP exportConfiguration) +QByteArray KisDocument::mimeType() const { - if (!prepareLocksForSaving()) { - return false; - } + return d->mimeType; +} - // Unset the error message - setErrorMessage(""); +void KisDocument::setMimeType(const QByteArray & mimeType) +{ + d->mimeType = mimeType; +} - // 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; +bool KisDocument::fileBatchMode() const +{ + return d->importExportManager->batchMode(); +} - //qDebug() << "saveFile" << localFilePath() << QFileInfo(localFilePath()).exists() << !QFileInfo(localFilePath()).isWritable(); +void KisDocument::setFileBatchMode(const bool batchMode) +{ + d->importExportManager->setBatchMode(batchMode); +} - if (QFileInfo(localFilePath()).exists() && !QFileInfo(localFilePath()).isWritable()) { - setErrorMessage(i18n("%1 cannot be written to. Please save under a different name.", localFilePath())); +KisDocument* KisDocument::lockAndCloneForSaving() +{ + Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); + if (!locker.successfullyLocked()) { + return 0; } - else { - - // The output format is set by KisMainWindow, and by openFile - QByteArray outputMimeType = d->outputMimeType; - - if (outputMimeType.isEmpty()) { - outputMimeType = d->outputMimeType = nativeFormatMimeType(); - } + return new KisDocument(*this); +} - //qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType; +bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) +{ + Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); + if (!locker.successfullyLocked()) { + return false; + } + d->savingImage = d->image; - if (d->backupFile) { - Q_ASSERT(url().isLocalFile()); - KBackup::backupFile(url().toLocalFile()); - } + const QString fileName = url.toLocalFile(); - qApp->processEvents(); + KisImportExportFilter::ConversionStatus status = + d->importExportManager-> + exportDocument(fileName, fileName, mimeType, false, exportConfiguration); - setFileProgressUpdater(i18n("Saving Document")); + d->savingImage = 0; - //qDebug() << "saving to tempory file" << tempororaryFileName; - status = d->importExportManager->exportDocument(localFilePath(), filePath, outputMimeType, !d->isExporting , exportConfiguration); + return status == KisImportExportFilter::OK; +} - ret = (status == KisImportExportFilter::OK); - suppressErrorDialog = (fileBatchMode() || isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph); - //qDebug() << "Export status was" << status; +bool KisDocument::initiateSavingInBackground(const QString actionName, + const QObject *receiverObject, const char *receiverMethod, + const KritaUtils::ExportFileJob &job, + KisPropertiesConfigurationSP exportConfiguration) +{ + KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); - if (ret) { + QScopedPointer clonedDocument(lockAndCloneForSaving()); - 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(); - } + // we block saving until the current saving is finished! + if (!clonedDocument || !d->savingMutex.tryLock()) { + return false; + } - 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); - } + KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); + KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); + d->backgroundSaveDocument.reset(clonedDocument.take()); + d->backgroundSaveJob = job; - d->mimeType = outputMimeType; - } + if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { + d->backgroundSaveDocument->d->isAutosaving = true; } - if (!ret) { - if (!suppressErrorDialog) { + connect(d->backgroundSaveDocument.data(), + SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)), + this, + SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&))); - 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())); - } + connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), + receiverObject, receiverMethod, Qt::UniqueConnection); - } + bool started = + d->backgroundSaveDocument->startExportInBackground(actionName, + job.filePath, + job.filePath, + job.mimeType, + job.flags & KritaUtils::SaveShowWarnings, + exportConfiguration); - // 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(); + return started; +} - // As we did not save, restore the "was modified" status - setModified(wasModified); + +void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +{ + KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { + d->savingMutex.unlock(); + return; } - emit sigSavingFinished(); - clearFileProgressUpdater(); + KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); - unlockAfterSaving(); - return ret; -} + if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { + d->backgroundSaveDocument->d->isAutosaving = false; + } + d->backgroundSaveDocument.take()->deleteLater(); + d->savingMutex.unlock(); -QByteArray KisDocument::mimeType() const -{ - return d->mimeType; -} + KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); + const KritaUtils::ExportFileJob job = d->backgroundSaveJob; + d->backgroundSaveJob = KritaUtils::ExportFileJob(); -void KisDocument::setMimeType(const QByteArray & mimeType) -{ - d->mimeType = mimeType; + emit sigCompleteBackgroundSaving(job, status, errorMessage); } -void KisDocument::setOutputMimeType(const QByteArray & mimeType) +void KisDocument::slotAutoSave() { - d->outputMimeType = mimeType; -} + if (!d->modified || !d->modifiedAfterAutosave) return; + const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); -QByteArray KisDocument::outputMimeType() const -{ - return d->outputMimeType; -} + bool started = + initiateSavingInBackground(i18n("Autosaving..."), + this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)), + KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), + 0); -bool KisDocument::fileBatchMode() const -{ - return d->importExportManager->batchMode(); + if (!started) { + const int emergencyAutoSaveInterval = 10; // sec + setAutoSaveDelay(emergencyAutoSaveInterval); + } else { + d->modifiedAfterAutosave = false; + } } -void KisDocument::setFileBatchMode(const bool batchMode) +void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { - d->importExportManager->setBatchMode(batchMode); -} + Q_UNUSED(job); -bool KisDocument::isImporting() const -{ - return d->isImporting; -} + const QString fileName = QFileInfo(job.filePath).fileName(); -bool KisDocument::isExporting() const -{ - return d->isExporting; + if (status != KisImportExportFilter::OK) { + const int emergencyAutoSaveInterval = 10; // sec + setAutoSaveDelay(emergencyAutoSaveInterval); + emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error mesage", + "Error during autosaving %1: %2", + fileName, + exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); + } else { + KisConfig cfg; + d->autoSaveDelay = cfg.autoSaveInterval(); + + if (!d->modifiedAfterAutosave) { + d->autoSaveTimer.stop(); // until the next change + } + + emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); + } } -void KisDocument::slotAutoSave() +bool KisDocument::startExportInBackground(const QString &actionName, + const QString &location, + const QString &realLocation, + const QByteArray &mimeType, + bool showWarnings, + KisPropertiesConfigurationSP exportConfiguration) { - //qDebug() << "slotAutoSave. Modified:" << d->modified << "modifiedAfterAutosave" << d->modified << "url" << url() << localFilePath(); + d->savingImage = d->image; - if (!d->isAutosaving && d->modified && d->modifiedAfterAutosave) { + KisMainWindow *window = KisPart::instance()->currentMainwindow(); + if (window) { + if (window->viewManager()) { + d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); + d->importExportManager->setUpdater(d->savingUpdater); + } + } - 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()); + d->childSavingFuture = + d->importExportManager->exportDocumentAsyc(location, + realLocation, + mimeType, + showWarnings, + exportConfiguration); - QByteArray mimetype = d->outputMimeType; - d->outputMimeType = nativeFormatMimeType(); - bool ret = exportDocument(QUrl::fromLocalFile(autoSaveFileName)); - d->outputMimeType = mimetype; + if (d->childSavingFuture.isCanceled()) return false; - if (ret) { - d->modifiedAfterAutosave = false; - d->autoSaveTimer.stop(); // until the next change - } - qApp->restoreOverrideCursor(); - d->importExportManager->setBatchMode(batchmode); - d->isAutosaving = false; + typedef QFutureWatcher StatusWatcher; + StatusWatcher *watcher = new StatusWatcher(); + watcher->setFuture(d->childSavingFuture); - emit clearStatusBarMessage(); - disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); + connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); + connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); - if (!ret && !d->disregardAutosaveFailure) { - emit statusBarMessage(i18n("Error during autosave! Partition full?")); - } + return true; +} + +void KisDocument::finishExportInBackground() +{ + KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { + emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); + return; + } + + KisImportExportFilter::ConversionStatus status = + d->childSavingFuture.result(); + const QString errorMessage = this->errorMessage(); + + d->savingImage.clear(); + d->childSavingFuture = QFuture(); + d->lastErrorMessage.clear(); + + if (d->savingUpdater) { + d->savingUpdater->setProgress(100); } + + emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setAutoSaveDelay(d->autoSaveDelay); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { //qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay; d->autoSaveDelay = delay; if (isReadWrite() && d->autoSaveDelay > 0) { d->autoSaveTimer.start(d->autoSaveDelay * 1000); } else { d->autoSaveTimer.stop(); } } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); + QRegularExpression autosavePattern("^\\..+-autosave.kra$"); + + QFileInfo fi(path); + QString dir = fi.absolutePath(); + QString filename = fi.fileName(); - if (path.isEmpty()) { + if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { - 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) +bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); - if (autosaveOpened) { - resetURL(); // Force save to act like 'Save As' + if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); + setRecovered(true); } else { - if( !(flags & OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES) ) { + if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } + + setRecovered(false); } + return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); - setWindowIcon(KisIconUtils::loadIcon("dialog-warning")); + setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); - labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32)); + labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

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

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; - setFileProgressUpdater(i18n("Opening Document")); + KisMainWindow *window = KisPart::instance()->currentMainwindow(); + if (window) { + if (window->viewManager()) { + KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); + d->importExportManager->setUpdater(updater); + } + } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } - 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(); - } + 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) +void KisDocument::slotUndoStackCleanChanged(bool value) { - // even if the document was already modified, call setModified to re-start autosave timer - setModified(idx != d->undoStack->cleanIndex()); + setModified(!value); +} + +void KisDocument::slotConfigChanged() +{ + KisConfig cfg; + d->undoStack->setUndoLimit(cfg.undoStackLimit()); + setAutoSaveDelay(cfg.autoSaveInterval()); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } - 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; } +bool KisDocument::isSaving() const +{ + const bool result = d->savingMutex.tryLock(); + if (result) { + d->savingMutex.unlock(); + } + return !result; +} + +void KisDocument::waitForSavingToComplete() +{ + KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); + f.waitForMutex(&d->savingMutex); +} + KoShapeBasedDocumentBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } -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() +QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { - d->savingImage = 0; - d->savingMutex.unlock(); + return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } - diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index 9bcab7504c..50af901602 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,634 +1,589 @@ /* 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. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include +#include #include #include #include #include +#include #include "kritaui_export.h" class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeBasedDocumentBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; -class KoProgressUpdater; -class KoProgressProxy; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPaintingAssistant; class KisPart; class KisGridConfig; class KisGuidesConfig; class QDomDocument; class KisPart; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); -public: + /** + * @brief KisDocument makes a deep copy of the document \p rhs. + * The caller *must* ensure that the image is properly + * locked and is in consistent state before asking for + * cloning. + * @param rhs the source document to copy from + */ + explicit KisDocument(const KisDocument &rhs); - enum OpenUrlFlags { - OPEN_URL_FLAG_NONE = 1 << 0, - OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES = 1 << 1, +public: + enum OpenFlag { + None = 0, + DontAddToRecent = 0x1, + RecoveryFile = 0x2 }; + Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); + + /** + * @brief creates a clone of the document and returns it. Please make sure that you + * hold all the necessary locks on the image before asking for a clone! + */ + KisDocument* clone(); + /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ - bool openUrl(const QUrl &url, OpenUrlFlags flags = OPEN_URL_FLAG_NONE); + bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. - * - * @note This will call KisDocument::saveAs(). To differentiate this - * from an ordinary Save operation (in any reimplementation of - * saveFile()) call isExporting(). */ - bool exportDocument(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration = 0); + bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); + + bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); + + bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; - /** - * @brief Set the format in which the document should be saved. - * - * This is called on loading, and in "save as", so you shouldn't - * have to call it. - * - * @param mimeType the mime type (format) to use. - */ - void setOutputMimeType(const QByteArray & mimeType) override; - QByteArray outputMimeType() const override; - /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; - /** - * @return the object to report progress to. - * - * This is only not zero if loading or saving is in progress. - * - * One can add more KoUpdaters to it to make the progress reporting more - * accurate. If no active progress reporter is present, 0 is returned. - **/ - KoProgressUpdater *progressUpdater() const; - - /** - * Set a custom progress proxy to use to report loading - * progress to. - */ - void setProgressProxy(KoProgressProxy *progressProxy); - KoProgressProxy* progressProxy() const; - /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(); - /** - * @brief setBackupFile enable/disable saving a backup of the file on saving - * @param saveBackup if true, Krita will save a backup of the file - */ - void setBackupFile(bool saveBackup); - /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); + void setRecovered(bool value); + bool isRecovered() const; + void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); - /** - * Progress info while loading or saving. The value is in percents (i.e. a number between 0 and 100) - * Your KisDocument-derived class should emit the signal now and then during load/save. - * KisMainWindow will take care of displaying a progress bar automatically. - */ - void sigProgress(int value); - - /** - * Progress cancel button pressed - * This is emitted by KisDocument - */ - void sigProgressCanceled(); - /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ - void statusBarMessage(const QString& text); + void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); + void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + + void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + +private Q_SLOTS: + void finishExportInBackground(); + void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + + void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); private: friend class KisPart; friend class SafeSavingLocker; + bool initiateSavingInBackground(const QString actionName, + const QObject *receiverObject, const char *receiverMethod, + const KritaUtils::ExportFileJob &job, + KisPropertiesConfigurationSP exportConfiguration); + + bool startExportInBackground(const QString &actionName, const QString &location, + const QString &realLocation, + const QByteArray &mimeType, + bool showWarnings, + KisPropertiesConfigurationSP exportConfiguration); + /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); - /** - * Saves a document - * - * Applies a filter if necessary, and calls exportDocument in any case - * You should not have to reimplement, except for very special cases. - */ - bool saveFile(const QString &filePath, KisPropertiesConfigurationSP exportConfiguration = 0); - - /** @internal */ void setModified(); - /** - * Returns whether or not the current openUrl() or openFile() call is - * actually an import operation (like File --> Import). - * This is for informational purposes only. - */ - bool isImporting() const; - - /** - * Returns whether or not the current saveFile() call is actually an export - * operation (like File --> Export). - * If this function returns true during saveFile() and you are changing - * some sort of state, you _must_ restore it before the end of saveFile(); - * otherwise, File --> Export will not work properly. - */ - bool isExporting() const; - public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); - bool saveAs(const QUrl &url, KisPropertiesConfigurationSP exportConfigration = 0); + bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &imageDescription, const double imageResolution); + bool isSaving() const; + void waitForSavingToComplete(); + KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; - /** - * Adds progressproxy for file operations - */ - void setFileProgressProxy(); - - /** - * Clears progressproxy for file operations - */ - void clearFileProgressProxy(); - - /** - * Adds progressupdater for file operations - */ - void setFileProgressUpdater(const QString &text); - - /** - * Clears progressupdater for file operations - */ - void clearFileProgressUpdater(); - /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeBasedDocumentBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** - * @return a list of all layers that are active in all current views - */ - vKisNodeSP activeNodes() const; - - /** - * set the list of nodes that were marked as currently active + * Set the list of nodes that was marked as currently active. Used *only* + * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** - * @return the node that was set as active during loading + * @return the node that was set as active during loading. Used *only* + * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; QList assistants() const; void setAssistants(const QList value); - bool save(KisPropertiesConfigurationSP exportConfiguration = 0); + bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); - /// Called by the undo stack when undo or redo is called - void slotUndoStackIndexChanged(int idx); + void slotUndoStackCleanChanged(bool value); + void slotConfigChanged(); -private: - bool prepareLocksForSaving(); +private: + /** + * @brief try to clone the image. This method handles all the locking for you. If locking + * has failed, no cloning happens + * @return cloned document on success, null otherwise + */ + KisDocument *lockAndCloneForSaving(); - void unlockAfterSaving(); + QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); class Private; Private *const d; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index 1d0d1796d2..17db104af2 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,522 +1,592 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisImportExportFilter.h" #include "KisDocument.h" #include #include #include "kis_painter.h" #include "kis_guides_config.h" #include "kis_grid_config.h" #include "kis_popup_button.h" #include #include "kis_async_action_feedback.h" // static cache for import and export mimetypes QStringList KisImportExportManager::m_importMimeTypes; QStringList KisImportExportManager::m_exportMimeTypes; class Q_DECL_HIDDEN KisImportExportManager::Private { public: bool batchMode {false}; - QPointer progressUpdater {0}; + KoUpdaterPtr updater; }; +struct KisImportExportManager::ConversionResult { + ConversionResult() + { + } + + ConversionResult(const QFuture &futureStatus) + : m_isAsync(true), + m_futureStatus(futureStatus) + { + } + + ConversionResult(KisImportExportFilter::ConversionStatus status) + : m_isAsync(false), + m_status(status) + { + } + + bool isAsync() const { + return m_isAsync; + } + + QFuture futureStatus() const { + return m_futureStatus; + } + + KisImportExportFilter::ConversionStatus status() const { + return m_status; + } + +private: + bool m_isAsync = false; + QFuture m_futureStatus; + KisImportExportFilter::ConversionStatus m_status = KisImportExportFilter::UsageError; +}; + + KisImportExportManager::KisImportExportManager(KisDocument* document) : m_document(document) , d(new Private) { } KisImportExportManager::~KisImportExportManager() { delete d; } KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType) { - return convert(Import, location, location, mimeType, false, 0); + ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); + + return result.status(); } -KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { - return convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration); + ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); + + return result.status(); +} + +QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +{ + ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync(), QFuture()); + + return result.futureStatus(); } // The static method to figure out to which parts of the // graph this mimetype has a connection to. QStringList KisImportExportManager::mimeFilter(Direction direction) { // Find the right mimetype by the extension QSet mimeTypes; // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; if (direction == KisImportExportManager::Import) { if (m_importMimeTypes.isEmpty()) { KoJsonTrader trader; QListlist = trader.query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_importMimeTypes = mimeTypes.toList(); } return m_importMimeTypes; } else if (direction == KisImportExportManager::Export) { if (m_exportMimeTypes.isEmpty()) { KoJsonTrader trader; QListlist = trader.query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_exportMimeTypes = mimeTypes.toList(); } return m_exportMimeTypes; } return QStringList(); } KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) { int weight = -1; KisImportExportFilter *filter = 0; QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } KisImportExportFilter *f = qobject_cast(obj); if (!f) { delete obj; continue; } int w = json.value("X-KDE-Weight").toInt(); if (w > weight) { delete filter; filter = f; f->setObjectName(loader->fileName()); weight = w; } } } qDeleteAll(list); if (filter) { filter->setMimeType(mimetype); } return filter; } void KisImportExportManager::setBatchMode(const bool batch) { d->batchMode = batch; } bool KisImportExportManager::batchMode(void) const { return d->batchMode; } -void KisImportExportManager::setProgresUpdater(KoProgressUpdater *updater) +void KisImportExportManager::setUpdater(KoUpdaterPtr updater) { - d->progressUpdater = updater; + d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@titile:window", "Open Audio")); return dialog.filename(); } -KisImportExportFilter::ConversionStatus KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location); } QSharedPointer filter(filterForMimeType(typeName, direction)); if (!filter) { return KisImportExportFilter::FilterCreationError; } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); - if (d->progressUpdater) { - filter->setUpdater(d->progressUpdater->startSubtask()); + if (!d->updater.isNull()) { + // WARNING: The updater is not guaranteed to be persistent! If you ever want + // to add progress reporting to "Save also as .kra", make sure you create + // a separate KoProgressUpdater for that! + + // WARNING2: the failsafe completion of the updater happens in the destructor + // the filter. + + filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, KisImportExportFilter::BadConversionGraph); - KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; + ConversionResult result = KisImportExportFilter::OK; if (direction == Import) { - status = doImport(location, filter); - } else /* if (direction == Export) */ { + // async importing is not yet supported! + KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); + + if (0 && !batchMode()) { + KisAsyncActionFeedback f(i18n("Opening document..."), 0); + result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); + } else { + result = doImport(location, filter); + } + } + else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; if (!askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra)) { return KisImportExportFilter::UserCancelled; } - if (!batchMode()) { + if (isAsync) { + result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); + } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); - status = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); + result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { - status = doExport(location, filter, exportConfiguration, alsoAsKra); + result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration) { KisConfig().setExportConfiguration(typeName, exportConfiguration); } } - return status; + return result; } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) { // Fill with some meta information about the image KisImageSP image = m_document->image(); KisPaintDeviceSP dev = image->projection(); const KoColorSpace* cs = dev->colorSpace(); const bool isThereAlpha = KisPainter::checkDeviceHasTransparency(image->projection()); exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); const bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10"))); exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); } bool KisImportExportManager::askUserAboutExportConfiguration( QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, const bool batchMode, const bool showWarnings, bool *alsoAsKra) { const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); QStringList warnings; QStringList errors; { KisPreExportChecker checker; checker.check(m_document->image(), filter->exportChecks()); warnings = checker.warnings(); errors = checker.errors(); } KisConfigWidget *wdg = filter->createConfigurationWidget(0, from, to); // Extra checks that cannot be done by the checker, because the checker only has access to the image. if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved.")); } if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved.")); } if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved.")); } if (!batchMode && !errors.isEmpty()) { QString error = "

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

" + "

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

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

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); } if (wdg) { QGroupBox *box = new QGroupBox(i18n("Options")); QVBoxLayout *boxLayout = new QVBoxLayout(box); wdg->setConfiguration(exportConfiguration); boxLayout->addWidget(wdg); layout->addWidget(box); } QCheckBox *chkAlsoAsKra = 0; if (showWarnings && !warnings.isEmpty()) { chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); chkAlsoAsKra->setChecked(KisConfig().readEntry("AlsoSaveAsKra", false)); layout->addWidget(chkAlsoAsKra); } dlg.setMainWidget(page); dlg.resize(dlg.minimumSize()); if (showWarnings || wdg) { if (!dlg.exec()) { return false; } } *alsoAsKra = false; if (chkAlsoAsKra) { KisConfig().writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); *alsoAsKra = chkAlsoAsKra->isChecked(); } if (wdg) { *exportConfiguration = *wdg->configuration(); } } return true; } KisImportExportFilter::ConversionStatus KisImportExportManager::doImport(const QString &location, QSharedPointer filter) { QFile file(location); if (!file.exists()) { return KisImportExportFilter::FileNotFound; } if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { return KisImportExportFilter::FileNotFound; } KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); if (file.isOpen()) { file.close(); } return status; } KisImportExportFilter::ConversionStatus KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) { KisImportExportFilter::ConversionStatus status = doExportImpl(location, filter, exportConfiguration); if (alsoAsKra && status == KisImportExportFilter::OK) { QString kraLocation = location + ".kra"; QByteArray mime = m_document->nativeFormatMimeType(); QSharedPointer filter( filterForMimeType(QString::fromLatin1(mime), Export)); KIS_SAFE_ASSERT_RECOVER_NOOP(filter); if (filter) { filter->setFilename(kraLocation); KisPropertiesConfigurationSP kraExportConfiguration = filter->lastSavedConfiguration(mime, mime); status = doExportImpl(kraLocation, filter, kraExportConfiguration); } else { status = KisImportExportFilter::FilterCreationError; } } return status; } KisImportExportFilter::ConversionStatus KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) { QSaveFile file(location); file.setDirectWriteFallback(true); if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { file.cancelWriting(); return KisImportExportFilter::CreationError; } KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, exportConfiguration); if (status != KisImportExportFilter::OK) { file.cancelWriting(); } else { file.commit(); } return status; } #include diff --git a/libs/ui/KisImportExportManager.h b/libs/ui/KisImportExportManager.h index 592050f946..600ded4ab2 100644 --- a/libs/ui/KisImportExportManager.h +++ b/libs/ui/KisImportExportManager.h @@ -1,155 +1,161 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_IMPORT_EXPORT_MANAGER_H #define KIS_IMPORT_EXPORT_MANAGER_H #include #include #include #include #include "KisImportExportFilter.h" #include "kritaui_export.h" class KisDocument; class KoProgressUpdater; +template +class QFuture; + /** * @brief The class managing all the filters. * * This class manages all filters for a %Calligra application. Normally * you will not have to use it, since KisMainWindow takes care of loading * and saving documents. * * @ref KisFilter * * @author Kalle Dalheimer * @author Torben Weis * @author Werner Trobin */ class KRITAUI_EXPORT KisImportExportManager : public QObject { Q_OBJECT public: /** * This enum is used to distinguish the import/export cases */ enum Direction { Import = 1, Export = 2 }; /** * Create a filter manager for a document */ explicit KisImportExportManager(KisDocument *document); public: ~KisImportExportManager() override; /** * Imports the specified document and returns the resultant filename * (most likely some file in /tmp). * @p path can be either a URL or a filename. * @p documentMimeType gives importDocument a hint about what type * the document may be. It can be left empty. * * @return status signals the success/error of the conversion. * If the QString which is returned isEmpty() and the status is OK, * then we imported the file directly into the document. */ KisImportExportFilter::ConversionStatus importDocument(const QString &location, const QString &mimeType); /** * @brief Exports the given file/document to the specified URL/mimetype. * * If @p mimeType is empty, then the closest matching Calligra part is searched * and when the method returns @p mimeType contains this mimetype. * Oh, well, export is a C++ keyword ;) */ - KisImportExportFilter::ConversionStatus exportDocument(const QString &location, const QString& realLocation, QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); + KisImportExportFilter::ConversionStatus exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); + + QFuture exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); ///@name Static API //@{ /** * Suitable for passing to KoFileDialog::setMimeTypeFilters. The default mime * gets set by the "users" of this method, as we do not have enough * information here. * Optionally, @p extraNativeMimeTypes are added after the native mimetype. */ static QStringList mimeFilter(Direction direction); /** * @brief filterForMimeType loads the relevant import/export plugin and returns it. The caller * is responsible for deleting it! * @param mimetype the mimetype we want to import/export. If there's more than one plugin, the one * with the highest weight as defined in the json description will be taken * @param direction import or export * @return a pointer to the filter plugin or 0 if none could be found */ static KisImportExportFilter *filterForMimeType(const QString &mimetype, Direction direction); /** * Set the filter manager is batch mode (no dialog shown) * instead of the interactive mode (dialog shown) */ void setBatchMode(const bool batch); /** * Get if the filter manager is batch mode (true) * or in interactive mode (true) */ bool batchMode(void) const; - void setProgresUpdater(KoProgressUpdater *updater); + void setUpdater(KoUpdaterPtr updater); static QString askForAudioFileName(const QString &defaultDir, QWidget *parent); private Q_SLOTS: private: - KisImportExportFilter::ConversionStatus convert(Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); + struct ConversionResult; + ConversionResult convert(Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync); void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration); bool askUserAboutExportConfiguration(QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, bool batchMode, const bool showWarnings, bool *alsoAsKra); KisImportExportFilter::ConversionStatus doImport(const QString &location, QSharedPointer filter); KisImportExportFilter::ConversionStatus doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra); KisImportExportFilter::ConversionStatus doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration); // Private API KisImportExportManager(const KisImportExportManager& rhs); KisImportExportManager &operator=(const KisImportExportManager& rhs); KisDocument *m_document; /// A static cache for the availability checks of filters static QStringList m_importMimeTypes; static QStringList m_exportMimeTypes; class Private; Private * const d; }; #endif // __KO_FILTER_MANAGER_H__ diff --git a/libs/image/kis_name_server.cpp b/libs/ui/KisImportExportUtils.cpp similarity index 66% copy from libs/image/kis_name_server.cpp copy to libs/ui/KisImportExportUtils.cpp index 4487daa05c..cc1582cdcc 100644 --- a/libs/image/kis_name_server.cpp +++ b/libs/ui/KisImportExportUtils.cpp @@ -1,44 +1,23 @@ /* - * Copyright (c) 2002 Patrick Julien + * 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 "kis_name_server.h" +#include "KisImportExportUtils.h" -KisNameServer::KisNameServer(qint32 seed) -{ - m_generator = seed; -} - -KisNameServer::~KisNameServer() -{ -} - -qint32 KisNameServer::currentSeed() const -{ - return m_generator; -} - -qint32 KisNameServer::number() -{ - return m_generator++; -} +namespace KritaUtils { -void KisNameServer::rollback() -{ - m_generator--; } - diff --git a/libs/ui/KisImportExportUtils.h b/libs/ui/KisImportExportUtils.h new file mode 100644 index 0000000000..18c4923a40 --- /dev/null +++ b/libs/ui/KisImportExportUtils.h @@ -0,0 +1,59 @@ +/* + * 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 KISIMPORTEXPORTUTILS_H +#define KISIMPORTEXPORTUTILS_H + +#include +#include + +namespace KritaUtils { + +enum SaveFlag { + SaveNone = 0, + SaveShowWarnings = 0x1, + SaveIsExporting = 0x2, + SaveInAutosaveMode = 0x4 +}; + +Q_DECLARE_FLAGS(SaveFlags, SaveFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(SaveFlags) + +struct ExportFileJob { + ExportFileJob() + : flags(SaveNone) + { + } + + ExportFileJob(QString _filePath, QByteArray _mimeType, SaveFlags _flags = SaveNone) + : filePath(_filePath), mimeType(_mimeType), flags(_flags) + { + } + + bool isValid() const { + return !filePath.isEmpty(); + } + + QString filePath; + QByteArray mimeType; + SaveFlags flags; +}; + +} + +#endif // KISIMPORTEXPORTUTILS_H diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index f25f9cadc1..00f8ae6edd 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2516 +1,2471 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDockWidgetTitleBar.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include #include #include #include +#include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "KisDocument.h" #include "KisDocument.h" #include "kis_group_layer.h" #include "kis_icon_utils.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_resource_server_provider.h" #include "kis_signal_compressor_with_param.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include "kis_animation_exporter.h" #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); dockWidget->setTabEnabled(false); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent) : q(parent) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; KisViewManager *viewManager {0}; QPointer activeView; - QPointer progress; - QPointer progressCancel; - QMutex progressMutex; - QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; - bool isImporting {false}; - bool isExporting {false}; bool noCleanup {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QString lastExportLocation; QMap dockWidgetsMap; QMap dockWidgetVisibilityMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow() : KXmlGuiWindow() , d(new Private(this)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(false); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); KisConfig cfg; d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setMainWindow(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); setCentralWidget(d->mdiArea); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); setAutoSaveSettings("krita", false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita.xmlgui")); setXMLFile(":/kxmlgui5/krita.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } plugActionList("toolbarlist", toolbarList); setToolbarList(toolbarList); applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } } void KisMainWindow::setNoCleanup(bool noCleanup) { d->noCleanup = noCleanup; } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); if (d->noCleanup) return; delete d->viewManager; delete d; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } + // register the newly created view in the input manager + viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); + showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { - connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified(QString,bool))); + connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); } } +void KisMainWindow::notifyChildViewDestroyed(KisView *view) +{ + viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); + if (view->canvasBase() == viewManager()->canvasBase()) { + viewManager()->setCurrentView(0); + } +} + + void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg; subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString(); // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) if (path.contains(*it)) ok = false; // it's in the tmp resource #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start - Q_FOREACH (KMainWindow* window, KMainWindow::memberList()) - static_cast(window)->reloadRecentFileList(); + Q_FOREACH (KMainWindow* window, KMainWindow::memberList()) { + /** + * FIXME: this is a hacking approach of reloading the updated recent files list. + * Sometimes, the result of reading from KConfig right after doing 'sync()' still + * returns old values of the recent files. Reading the same files a bit later + * returns correct "updated" files. I couldn't find the cause of it (DK). + */ + + KisMainWindow *mw = static_cast(window); + + if (mw != this) { + mw->reloadRecentFileList(); + } + } } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document()){ - QString caption( d->activeView->document()->caption() ); + KisDocument *doc = d->activeView->document(); + + QString caption(doc->caption()); if (d->readOnly) { caption += ' ' + i18n("(write protected)"); } + if (doc->isRecovered()) { + caption += ' ' + i18n("[RECOVERED]"); + } + + caption += "[*]"; + d->activeView->setWindowTitle(caption); + d->activeView->setWindowModified(doc->isModified()); - updateCaption(caption, d->activeView->document()->isModified()); + updateCaption(caption, doc->isModified()); - if (!d->activeView->document()->url().fileName().isEmpty()) - d->saveAction->setToolTip(i18n("Save as %1", d->activeView->document()->url().fileName())); + if (!doc->url().fileName().isEmpty()) + d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } -bool KisMainWindow::openDocument(const QUrl &url) +bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { - QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); + if (!flags && BatchMode) { + QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); + } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } - return openDocumentInternal(url); + return openDocumentInternal(url, flags); } -bool KisMainWindow::openDocumentInternal(const QUrl &url, KisDocument *newdoc) +bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } - if (!newdoc) { - newdoc = KisPart::instance()->createDocument(); + KisDocument *newdoc = KisPart::instance()->createDocument(); + + if (flags & BatchMode) { + newdoc->setFileBatchMode(true); } d->firstTime = true; - connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); - bool openRet = (!d->isImporting) ? newdoc->openUrl(url) : newdoc->importDocument(url); + + KisDocument::OpenFlags openFlags = KisDocument::None; + if (flags & RecoveryFile) { + openFlags |= KisDocument::RecoveryFile; + } + + bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); + + if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } -void KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) +KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); + + return view; } -QStringList KisMainWindow::showOpenFileDialog() +QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); - dialog.setCaption(d->isImporting ? i18n("Import Images") : i18n("Open Images")); + dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); - disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); - disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); - disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } -bool KisMainWindow::saveDocument(KisDocument *document, bool saveas) +bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } + if (document->isRecovered()) { + saveas = true; + } + bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } - connect(document, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); - QByteArray _native_format = document->nativeFormatMimeType(); - QByteArray oldOutputFormat = document->outputMimeType(); + QByteArray nativeFormat = document->nativeFormatMimeType(); + QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); - if (!mimeFilter.contains(oldOutputFormat)) { - dbgUI << "KisMainWindow::saveDocument no export filter for" << oldOutputFormat; + mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); + if (!mimeFilter.contains(oldMimeFormat)) { + dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name - suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(_native_format).first().remove("*."); + suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first().remove("*."); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; - if (document->url().isEmpty() || d->isExporting || saveas) { + if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); - dialog.setCaption(d->isExporting ? i18n("Exporting") : i18n("Saving As")); + dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); - /*qDebug() << ">>>>>" << d->isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->outputMimeType())*/; + //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); - if (d->isExporting && !d->lastExportLocation.isEmpty()) { + if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QDesktopServices::storageLocation(QDesktopServices::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; - if (!d->isExporting) { + if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. - default_mime_type = document->outputMimeType().isEmpty() ? _native_format : document->outputMimeType(); + default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } - QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { - fn.append(KisMimeDatabase::suffixesForMimeType(_native_format).first()); + fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } - QByteArray outputFormat = _native_format; + QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile()); outputFormat = outputFormatString.toLatin1(); - if (!d->isExporting) { + if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { - // - // Note: - // If the user is stupid enough to Export to the current URL, - // we do _not_ change this operation into a Save As. Reasons - // follow: - // - // 1. A check like "d->isExporting && oldURL == newURL" - // doesn't _always_ work on case-insensitive filesystems - // and inconsistent behaviour is bad. - // 2. It is probably not a good idea to change document->mimeType - // and friends because the next time the user File/Save's, - // (not Save As) they won't be expecting that they are - // using their File/Export settings - // - // As a bad side-effect of this, the modified flag will not - // be updated and it is possible that what is currently on - // their screen is not what is stored on disk (through loss - // of formatting). But if you are dumb enough to change - // mimetype but not the filename, then arguably, _you_ are - // the "bug" :) - // - // - Clarence - // - document->setOutputMimeType(outputFormat); - if (d->isExporting) { - // Export - ret = document->exportDocument(newURL); - - if (ret) { - // a few file dialog convenience things - d->lastExportLocation = newURL.toLocalFile(); - d->lastExportedFormat = outputFormat; - } - - // always restore output format - document->setOutputMimeType(oldOutputFormat); - - } - else { - // Save As - ret = document->saveAs(newURL); - + if (!isExporting) { // Save As + ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; - addRecentURL(newURL); + KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); - document->setOutputMimeType(oldOutputFormat); + } + } + else { // Export + ret = document->exportDocument(newURL, outputFormat); + + if (ret) { + d->lastExportLocation = newURL.toLocalFile(); + d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving + // We cannot "export" into the currently + // opened document. We are not Gimp. + KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! - if (d->isExporting || document->isModified()) { - ret = document->save(); + if (document->isModified()) { + ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } + if (ret && !isExporting) { + document->setRecovered(false); + } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->undoAction()->trigger(); d->undo->setText(activeView()->undoAction()->text()); } } void KisMainWindow::redo() { if (activeView()) { activeView()->redoAction()->trigger(); d->redo->setText(activeView()->redoAction()->text()); } } void KisMainWindow::closeEvent(QCloseEvent *e) { d->mdiArea->closeAllSubWindows(); QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); cfg.writeEntry("ko_geometry", saveGeometry().toBase64()); cfg.writeEntry("ko_windowstate", saveState().toBase64()); { KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); } QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); if (d->noCleanup) return; if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap) dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = config->group("MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); // Save collapsable state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text()); actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text()); d->viewManager->setCurrentView(view); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { - openDocument(url); + openDocument(url, None); } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); KisConfig cfg; int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } -void KisMainWindow::slotFileOpen() +void KisMainWindow::slotImportFile() { - QStringList urls = showOpenFileDialog(); + dbgUI << "slotImportFile()"; + slotFileOpen(true); +} + + +void KisMainWindow::slotFileOpen(bool isImporting) +{ + QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { - bool res = openDocument(QUrl::fromLocalFile(url)); + OpenFlags flags = isImporting ? Import : None; + bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { - (void) openDocument(QUrl::fromLocalFile(url.toLocalFile())); + (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { - if (saveDocument(d->activeView->document())) { + if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { - if (saveDocument(d->activeView->document(), true)) { + if (saveDocument(d->activeView->document(), true, false)) { + emit documentSaved(); + } +} + +void KisMainWindow::slotExportFile() +{ + if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } bool KisMainWindow::restoreWorkspace(const QByteArray &state) { QByteArray oldState = saveState(); const bool showTitlebars = KisConfig().showDockerTitleBars(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->hide(); dock->titleBarWidget()->setVisible(showTitlebars); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating()); } } return false; } Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed)); } } return success; } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { if(!slotFileCloseAll()) return; close(); Q_FOREACH (QPointer mainWin, KisPart::instance()->mainWindows()) { if (mainWin != this) { if(!mainWin->slotFileCloseAll()) return; mainWin->close(); } } } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); - document->setFileProgressProxy(); - document->setFileProgressUpdater(i18n("Import frames")); - KisAnimationImporter importer(document); + KoUpdaterPtr updater = + !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; + KisAnimationImporter importer(document->image(), updater); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); - document->clearFileProgressUpdater(); - document->clearFileProgressProxy(); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(KSharedConfig::openConfig()->group("krita")); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg; cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } -void KisMainWindow::slotProgress(int value) -{ - qApp->processEvents(); - - StdLockableWrapper wrapper(&d->progressMutex); - std::unique_lock> l(wrapper, std::try_to_lock); - if (!l.owns_lock()) return; - - - dbgUI << "KisMainWindow::slotProgress" << value; - if (value <= -1 || value >= 100) { - if (d->progress) { - statusBar()->removeWidget(d->progress); - delete d->progress; - d->progress = 0; - - disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); - statusBar()->removeWidget(d->progressCancel); - delete d->progressCancel; - d->progressCancel = 0; - } - d->firstTime = true; - return; - } - if (d->firstTime || !d->progress) { - // The statusbar might not even be created yet. - // So check for that first, and create it if necessary - QStatusBar *bar = findChild(); - if (!bar) { - statusBar()->show(); - QApplication::sendPostedEvents(this, QEvent::ChildAdded); - } - - if (d->progress) { - statusBar()->removeWidget(d->progress); - delete d->progress; - d->progress = 0; - - disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); - statusBar()->removeWidget(d->progressCancel); - delete d->progressCancel; - d->progress = 0; - } - - d->progressCancel = new QToolButton(statusBar()); - d->progressCancel->setMaximumHeight(statusBar()->fontMetrics().height()); - d->progressCancel->setIcon(KisIconUtils::loadIcon("process-stop")); - statusBar()->addPermanentWidget(d->progressCancel); - - d->progress = new QProgressBar(statusBar()); - d->progress->setMaximumHeight(statusBar()->fontMetrics().height()); - d->progress->setRange(0, 100); - statusBar()->addPermanentWidget(d->progress); - - connect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); - - d->progress->show(); - d->progressCancel->show(); - d->firstTime = false; - } - if (!d->progress.isNull()) { - d->progress->setValue(value); - } - qApp->processEvents(); -} - -void KisMainWindow::slotProgressCanceled() -{ - emit sigProgressCanceled(); -} - void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } -void KisMainWindow::slotImportFile() -{ - dbgUI << "slotImportFile()"; - - d->isImporting = true; - slotFileOpen(); - d->isImporting = false; -} - -void KisMainWindow::slotExportFile() -{ - dbgUI << "slotExportFile()"; - - d->isExporting = true; - slotFileSaveAs(); - d->isExporting = false; -} - - QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } KoDockWidgetTitleBar *titleBar = dynamic_cast(dockWidget->titleBarWidget()); // Check if the dock widget is supposed to be collapsable if (!dockWidget->titleBarWidget()) { titleBar = new KoDockWidgetTitleBar(dockWidget); dockWidget->setTitleBarWidget(titleBar); titleBar->setCollapsable(factory->isCollapsable()); } titleBar->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } bool collapsed = factory->defaultCollapsed(); bool locked = false; group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); collapsed = group.readEntry("Collapsed", collapsed); locked = group.readEntry("Locked", locked); //dbgKrita << "docker" << factory->id() << dockWidget << "collapsed" << collapsed << "locked" << locked << "titlebar" << titleBar; if (titleBar && collapsed) titleBar->setCollapsed(true); if (titleBar && locked) titleBar->setLocked(true); d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::setToolbarList(QList toolbarList) { qDeleteAll(d->toolbarList); d->toolbarList = toolbarList; } -void KisMainWindow::slotDocumentTitleModified(const QString &caption, bool mod) +void KisMainWindow::slotDocumentTitleModified() { updateCaption(); - updateCaption(caption, mod); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer(false)->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); auto ds = w->dockerState(); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(ds); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->resourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); } else { text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg; QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); + + /** + * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): + * + * If you make a window "Show on top" and then switch to the tabbed mode + * the window will contiue to be painted in its initial "mid-screen" + * position. It will persist here until you explicitly switch to its tab. + */ + if (viewMode == QMdiArea::TabbedView) { + Qt::WindowFlags oldFlags = subwin->windowFlags(); + Qt::WindowFlags flags = oldFlags; + + flags &= ~Qt::WindowStaysOnTopHint; + flags &= ~Qt::WindowStaysOnBottomHint; + + if (flags != oldFlags) { + subwin->setWindowFlags(flags); + subwin->showMaximized(); + } + } + } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } -void KisMainWindow::newView(QObject *document) +KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); - addViewAndNotifyLoadingCompleted(doc); + KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); + + return view; } void KisMainWindow::newWindow() { KisPart::instance()->createMainWindow()->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointerKisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { - title = d->activeView->document()->url().fileName(); - // strip off the native extension (I don't want foobar.kwd.ps when printing into a file) - QString extension = KisMimeDatabase::suffixesForMimeType(d->activeView->document()->outputMimeType()).first(); - if (title.endsWith(extension)) { - title.chop(extension.length()); - } + QFileInfo info(d->activeView->document()->url().fileName()); + title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); KisConfig cfg; actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); d->toggleDockers = actionManager->createAction("view_toggledockers"); cfg.showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars"); d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars()); connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createAction("file_close"); connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } restoreWorkspace(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray()))); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::showDockerTitleBars(bool show) { Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(show || (dock->isFloating() && isCollapsed)); } } KisConfig cfg; cfg.setShowDockerTitleBars(show); } void KisMainWindow::moveEvent(QMoveEvent *e) { if (qApp->desktop()->screenNumber(this) != qApp->desktop()->screenNumber(e->oldPos())) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/KisMainWindow.h b/libs/ui/KisMainWindow.h index 1e0ac2bbf4..50d9576eca 100644 --- a/libs/ui/KisMainWindow.h +++ b/libs/ui/KisMainWindow.h @@ -1,459 +1,468 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2004 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. */ #ifndef KIS_MAIN_WINDOW_H #define KIS_MAIN_WINDOW_H #include "kritaui_export.h" #include #include #include #include #include #include #include "KisView.h" class QCloseEvent; class QMoveEvent; struct KoPageLayout; class KoCanvasResourceManager; class KisDocument; class KisPrintJob; class KoDockFactoryBase; class QDockWidget; class KisView; class KisViewManager; class KoCanvasController; /** * @brief Main window for Krita * * This class is used to represent a main window within a Krita session. Each * main window contains a menubar and some toolbars, and potentially several * views of several canvases. * */ class KRITAUI_EXPORT KisMainWindow : public KXmlGuiWindow, public KoCanvasSupervisor { Q_OBJECT +public: + enum OpenFlag { + None = 0, + Import = 0x1, + BatchMode = 0x2, + RecoveryFile = 0x4 + }; + Q_DECLARE_FLAGS(OpenFlags, OpenFlag) + public: /** * Constructor. * * Initializes a Calligra main window (with its basic GUI etc.). */ explicit KisMainWindow(); /** * Destructor. */ ~KisMainWindow() override; /** * Update caption from document info - call when document info * (title in the about page) changes. */ void updateCaption(); // If noCleanup is set, KisMainWindow will not delete the root document // or part manager on destruction. void setNoCleanup(bool noCleanup); /** * @brief showView shows the given view. Override this if you want to show * the view in a different way than by making it the central widget, for instance * as an QMdiSubWindow */ virtual void showView(KisView *view); /** * @returns the currently active view */ KisView *activeView() const; /** * Sets the maximum number of recent documents entries. */ void setMaxRecentItems(uint _number); /** * The document opened a URL -> store into recent documents list. */ void addRecentURL(const QUrl &url); /** * Load the desired document and show it. * @param url the URL to open * * @return TRUE on success. */ - bool openDocument(const QUrl &url); + bool openDocument(const QUrl &url, OpenFlags flags); + + /** + * Saves the document, asking for a filename if necessary. + * + * @param saveas if set to TRUE the user is always prompted for a filename + * @param silent if set to TRUE rootDocument()->setTitleModified will not be called. + * + * @return TRUE on success, false on error or cancel + * (don't display anything in this case, the error dialog box is also implemented here + * but restore the original URL in slotFileSaveAs) + */ + bool saveDocument(KisDocument *document, bool saveas, bool isExporting); + void setReadWrite(bool readwrite); /// Return the list of dock widgets belonging to this main window. QList dockWidgets() const; QDockWidget* dockWidget(const QString &id); QList canvasObservers() const override; KoCanvasResourceManager *resourceManager() const; int viewCount() const; /** * A wrapper around restoreState * @param state the saved state * @return TRUE on success */ bool restoreWorkspace(const QByteArray &state); KisViewManager *viewManager() const; - void addViewAndNotifyLoadingCompleted(KisDocument *document); + KisView *addViewAndNotifyLoadingCompleted(KisDocument *document); - QStringList showOpenFileDialog(); + QStringList showOpenFileDialog(bool isImporting); /** * Shows if the main window is saving anything right now. If the * user presses Ctrl+W too fast, then the document can be close * before the saving is completed. I'm not sure if it is fixable * in any way without avoiding using porcessEvents() * everywhere (DK) * * Don't use it unless you have no option. */ bool hackIsSaving() const; Q_SIGNALS: /** * This signal is emitted if the document has been saved successfully. */ void documentSaved(); /// This signal is emitted when this windows has finished loading of a /// document. The document may be opened in another window in the end. /// In this case, the signal means there is no link between the window /// and the document anymore. void loadCompleted(); /// This signal is emitted right after the docker states have been succefully restored from config void restoringDone(); /// This signal is emitted when the color theme changes void themeChanged(); /// This signal is emitted when the shortcut key configuration has changed void keyBindingsChanged(); void guiLoadingFinished(); - /// This signal is emitted when the user clicked on the progressbar cancel - void sigProgressCanceled(); - public Q_SLOTS: /** * Slot for opening a new document. * * If the current document is empty, the new document replaces it. * If not, a new mainwindow will be opened for showing the document. */ void slotFileNew(); /** * Slot for opening a saved file. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ - void slotFileOpen(); + void slotFileOpen(bool isImporting = false); /** * Slot for opening a file among the recently opened files. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpenRecent(const QUrl &); /** * @brief slotPreferences open the preferences dialog */ void slotPreferences(); /** * Saves the current document with the current name. */ void slotFileSave(); // XXX: disabled KisPrintJob* exportToPdf(QString pdfFileName = QString()); - void slotProgress(int value); - - /** - * Saves the document, asking for a filename if necessary. - * - * @param saveas if set to TRUE the user is always prompted for a filename - * @param silent if set to TRUE rootDocument()->setTitleModified will not be called. - * - * @return TRUE on success, false on error or cancel - * (don't display anything in this case, the error dialog box is also implemented here - * but restore the original URL in slotFileSaveAs) - */ - bool saveDocument(KisDocument *document, bool saveas = false); - /** * Update the option widgets to the argument ones, removing the currently set widgets. */ void newOptionWidgets(KoCanvasController *controller, const QList > & optionWidgetList); + KisView *newView(QObject *document); + + void notifyChildViewDestroyed(KisView *view); private Q_SLOTS: /** * Save the list of recent files. */ void saveRecentFiles(); void slotLoadCompleted(); void slotLoadCanceled(const QString &); void slotSaveCompleted(); void slotSaveCanceled(const QString &); void forceDockTabFonts(); - void slotProgressCanceled(); - /** * @internal */ - void slotDocumentTitleModified(const QString &caption, bool mod); + void slotDocumentTitleModified(); /** * Prints the actual document. */ void slotFilePrint(); /** * Saves the current document with a new name. */ void slotFileSaveAs(); void slotFilePrintPreview(); void importAnimation(); /** * Show a dialog with author and document information. */ void slotDocumentInfo(); /** * Closes all open documents. */ bool slotFileCloseAll(); /** * @brief showAboutApplication show the about box */ virtual void showAboutApplication(); /** * Closes the mainwindow. */ void slotFileQuit(); /** * Configure toolbars. */ void slotConfigureToolbars(); /** * Post toolbar config. * (Plug action lists back in, etc.) */ void slotNewToolbarConfig(); /** * Shows or hides a toolbar */ void slotToolbarToggled(bool toggle); /** * Toggle full screen on/off. */ void viewFullscreen(bool fullScreen); /** * Toggle docker titlebars on/off. */ void showDockerTitleBars(bool show); /** * Reload file */ void slotReloadFile(); /** * File --> Import * * This will call slotFileOpen(). */ void slotImportFile(); /** * File --> Export * * This will call slotFileSaveAs(). */ void slotExportFile(); /** * Hide the dockers */ void toggleDockersVisibility(bool visible); /** * Handle theme changes from theme manager */ void slotThemeChanged(); void undo(); void redo(); void updateWindowMenu(); void setActiveSubWindow(QWidget *window); void configChanged(); - void newView(QObject *document); + void newWindow(); void closeCurrentWindow(); void checkSanity(); /// Quits Krita with error message from m_errorMessage. void showErrorAndDie(); protected: void closeEvent(QCloseEvent * e) override; void resizeEvent(QResizeEvent * e) override; // QWidget overrides void dragEnterEvent(QDragEnterEvent * event) override; void dropEvent(QDropEvent * event) override; void dragMoveEvent(QDragMoveEvent * event) override; void dragLeaveEvent(QDragLeaveEvent * event) override; void setToolbarList(QList toolbarList); - -public Q_SLOTS: +private: /** * Add a the given view to the list of views of this mainwindow. * This is a private implementation. For public usage please use * newView() and addViewAndNotifyLoadingCompleted(). */ void addView(KisView *view); +public Q_SLOTS: + /// Set the active view, this will update the undo/redo actions void setActiveView(KisView *view); void subWindowActivated(); private: friend class KisApplication; /** * Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created. * Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets. * @param factory the factory used to create the dock widget if needed * @return the dock widget specified by @p factory (may be 0) */ QDockWidget* createDockWidget(KoDockFactoryBase* factory); - bool openDocumentInternal(const QUrl &url, KisDocument *newdoc = 0); + bool openDocumentInternal(const QUrl &url, KisMainWindow::OpenFlags flags = 0); /** * Reloads the recent documents list. */ void reloadRecentFileList(); /** * Updates the window caption based on the document info and path. */ void updateCaption(const QString & caption, bool mod); void updateReloadFileAction(KisDocument *doc); void saveWindowSettings(); QPointeractiveKisView(); void applyDefaultSettings(QPrinter &printer); void createActions(); void applyToolBarLayout(); protected: void moveEvent(QMoveEvent *e) override; private Q_SLOTS: void initializeGeometry(); void showManual(); void switchTab(int index); private: /** * Struct used in the list created by createCustomDocumentWidgets() */ struct CustomDocumentWidgetItem { /// Pointer to the custom document widget QWidget *widget; /// title used in the sidebar. If left empty it will be displayed as "Custom Document" QString title; /// icon used in the sidebar. If left empty it will use the unknown icon QString icon; }; class Private; Private * const d; QString m_errorMessage; bool m_dieOnError; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(KisMainWindow::OpenFlags) + #endif diff --git a/libs/ui/KisPaletteModel.cpp b/libs/ui/KisPaletteModel.cpp index fc18a79964..35d0e30b62 100644 --- a/libs/ui/KisPaletteModel.cpp +++ b/libs/ui/KisPaletteModel.cpp @@ -1,625 +1,632 @@ /* * Copyright (c) 2013 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 "KisPaletteModel.h" #include #include #include #include #include #include #include #include #include #include KisPaletteModel::KisPaletteModel(QObject* parent) : QAbstractTableModel(parent), m_colorSet(0), m_displayRenderer(KoDumbColorDisplayRenderer::instance()) { } KisPaletteModel::~KisPaletteModel() { } void KisPaletteModel::setDisplayRenderer(KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_displayRenderer) { disconnect(m_displayRenderer, 0, this, 0); } m_displayRenderer = displayRenderer; connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged())); } else { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KisPaletteModel::slotDisplayConfigurationChanged() { reset(); } QModelIndex KisPaletteModel::getLastEntryIndex() { int endRow = rowCount(); int endColumn = columnCount(); if (m_colorSet->nColors()>0) { QModelIndex i = this->index(endRow, endColumn, QModelIndex()); while (qVariantValue(i.data(RetrieveEntryRole)).isEmpty()) { i = this->index(endRow, endColumn); endColumn -=1; if (endColumn<0) { endColumn = columnCount(); endRow-=1; } } return i; } return QModelIndex(); } QVariant KisPaletteModel::data(const QModelIndex& index, int role) const { KoColorSetEntry entry; if (m_colorSet && m_displayRenderer) { //now to figure out whether we have a groupname row or not. bool groupNameRow = false; quint32 indexInGroup = 0; QString indexGroupName = QString(); int rowstotal = m_colorSet->nColorsGroup()/columnCount(); if (index.row()<=rowstotal && (quint32)(index.row()*columnCount()+index.column())nColorsGroup()) { indexInGroup = (quint32)(index.row()*columnCount()+index.column()); } if (m_colorSet->nColorsGroup()==0) { rowstotal+=1; //always add one for the default group when considering groups. } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ //we make an int for the rows added by the current group. int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount(); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { newrows+=1; } if (newrows==0) { newrows+=1; //always add one for the group when considering groups. } quint32 tempIndex = (quint32)((index.row()-(rowstotal+2))*columnCount()+index.column()); if (index.row() == rowstotal+1) { //rowstotal+1 is taken up by the groupname. indexGroupName = groupName; groupNameRow = true; } else if (index.row() > (rowstotal+1) && index.row() <= rowstotal+newrows && tempIndexnColorsGroup(groupName)){ //otherwise it's an index to the colors in the group. indexGroupName = groupName; indexInGroup = tempIndex; } //add the new rows to the totalrows we've looked at. rowstotal += newrows; } if (groupNameRow) { switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return indexGroupName; } case IsHeaderRole: { return true; } case RetrieveEntryRole: { QStringList entryList; entryList.append(indexGroupName); entryList.append(QString::number(0)); return entryList; } } } else { if (indexInGroup < m_colorSet->nColorsGroup(indexGroupName)) { entry = m_colorSet->getColorGroup(indexInGroup, indexGroupName); switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return entry.name; } case Qt::BackgroundRole: { QColor color = m_displayRenderer->toQColor(entry.color); return QBrush(color); } case IsHeaderRole: { return false; } case RetrieveEntryRole: { QStringList entryList; entryList.append(indexGroupName); entryList.append(QString::number(indexInGroup)); return entryList; } } } } } return QVariant(); } int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const { if (!m_colorSet) { return 0; } if (columnCount() > 0) { int countedrows = m_colorSet->nColorsGroup("")/columnCount(); if (m_colorSet->nColorsGroup()%columnCount() > 0) { countedrows+=1; } if (m_colorSet->nColorsGroup()==0) { countedrows+=1; } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()) { countedrows += 1; //add one for the name; countedrows += 1+(m_colorSet->nColorsGroup(groupName)/ columnCount()); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { countedrows+=1; } if (m_colorSet->nColorsGroup(groupName)==0) { countedrows+=1; } } countedrows +=1; //Our code up till now doesn't take 0 into account. return countedrows; } return m_colorSet->nColors()/15 + 1; } int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const { if (m_colorSet && m_colorSet->columnCount() > 0) { return m_colorSet->columnCount(); } return 15; } Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const { if (m_colorSet) { //make an int to hold the amount of rows we've looked at. The initial is the total rows in the default group. int rowstotal = m_colorSet->nColorsGroup()/columnCount(); if (row<=rowstotal && (quint32)(row*columnCount()+column)nColorsGroup()) { //if the total rows are in the default group, we just return an index. return QAbstractTableModel::index(row, column, parent); } else if(row<0 && column<0) { return QAbstractTableModel::index(0, 0, parent); } if (m_colorSet->nColorsGroup()==0) { rowstotal+=1; //always add one for the default group when considering groups. } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ //we make an int for the rows added by the current group. int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount(); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { newrows+=1; } if (m_colorSet->nColorsGroup(groupName)==0) { newrows+=1; //always add one for the group when considering groups. } if (rowstotal + newrows>rowCount()) { newrows = rowCount() - rowstotal; } quint32 tempIndex = (quint32)((row-(rowstotal+2))*columnCount()+column); if (row == rowstotal+1) { //rowstotal+1 is taken up by the groupname. return QAbstractTableModel::index(row, 0, parent); } else if (row > (rowstotal+1) && row <= rowstotal+newrows && tempIndexnColorsGroup(groupName)){ //otherwise it's an index to the colors in the group. return QAbstractTableModel::index(row, column, parent); } //add the new rows to the totalrows we've looked at. rowstotal += newrows; } } return QModelIndex(); } void KisPaletteModel::setColorSet(KoColorSet* colorSet) { m_colorSet = colorSet; reset(); } KoColorSet* KisPaletteModel::colorSet() const { return m_colorSet; } QModelIndex KisPaletteModel::indexFromId(int i) const { QModelIndex index = QModelIndex(); if (colorSet()->nColors()==0) { return index; } if (i > colorSet()->nColors()) { qWarning()<<"index is too big"<nColors(); index = this->index(0,0); } if (i < (int)colorSet()->nColorsGroup(0)) { index = QAbstractTableModel::index(i/columnCount(), i%columnCount()); if (!index.isValid()) { index = QAbstractTableModel::index(0,0,QModelIndex()); } return index; } else { int rowstotal = 1+m_colorSet->nColorsGroup()/columnCount(); if (m_colorSet->nColorsGroup()==0) { rowstotal +=1; } int totalIndexes = colorSet()->nColorsGroup(); Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ if (i+1<=totalIndexes+colorSet()->nColorsGroup(groupName) && i+1>totalIndexes) { int col = (i-totalIndexes)%columnCount(); int row = rowstotal+1+((i-totalIndexes)/columnCount()); index = this->index(row, col); return index; } else { rowstotal += 1+m_colorSet->nColorsGroup(groupName)/columnCount(); totalIndexes += colorSet()->nColorsGroup(groupName); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { rowstotal+=1; } if (m_colorSet->nColorsGroup(groupName)==0) { rowstotal+=1; //always add one for the group when considering groups. } } } } return index; } int KisPaletteModel::idFromIndex(const QModelIndex &index) const { if (index.isValid()==false) { return -1; qWarning()<<"invalid index"; } int i=0; QStringList entryList = qVariantValue(data(index, RetrieveEntryRole)); if (entryList.isEmpty()) { return -1; qWarning()<<"invalid index, there's no data to retreive here"; } if (entryList.at(0)==QString()) { return entryList.at(1).toUInt(); } i = colorSet()->nColorsGroup(""); //find at which position the group is. int groupIndex = colorSet()->getGroupNames().indexOf(entryList.at(0)); //add all the groupsizes onto it till we get to our group. for(int g=0; gnColorsGroup(colorSet()->getGroupNames().at(g)); } //then add the index. i += entryList.at(1).toUInt(); return i; } KoColorSetEntry KisPaletteModel::colorSetEntryFromIndex(const QModelIndex &index) const { + KoColorSetEntry blank = KoColorSetEntry(); + if (!index.isValid()) { + return blank; + } QStringList entryList = qVariantValue(data(index, RetrieveEntryRole)); + if (entryList.isEmpty()) { + return blank; + } QString groupName = entryList.at(0); quint32 indexInGroup = entryList.at(1).toUInt(); return m_colorSet->getColorGroup(indexInGroup, groupName); } bool KisPaletteModel::addColorSetEntry(KoColorSetEntry entry, QString groupName) { int col = m_colorSet->nColorsGroup(groupName)%columnCount(); QModelIndex i = getLastEntryIndex(); if (col+1>columnCount()) { beginInsertRows(QModelIndex(), i.row(), i.row()+1); } if (m_colorSet->nColors()nColors(), m_colorSet->nColors()+1); } m_colorSet->add(entry, groupName); if (col+1>columnCount()) { endInsertRows(); } if (m_colorSet->nColors()(index.data(RetrieveEntryRole)); if (entryList.empty()) { return false; } QString groupName = entryList.at(0); quint32 indexInGroup = entryList.at(1).toUInt(); if (qVariantValue(index.data(IsHeaderRole))==false) { if (index.column()-1<0 && m_colorSet->nColorsGroup(groupName)%columnCount() <1 && index.row()-1>0 && m_colorSet->nColorsGroup(groupName)/columnCount()>0) { beginRemoveRows(QModelIndex(), index.row(), index.row()-1); } m_colorSet->removeAt(indexInGroup, groupName); if (index.column()-1<0 && m_colorSet->nColorsGroup(groupName)%columnCount() <1 && index.row()-1>0 && m_colorSet->nColorsGroup(groupName)/columnCount()>0) { endRemoveRows(); } } else { beginRemoveRows(QModelIndex(), index.row(), index.row()-1); m_colorSet->removeGroup(groupName, keepColors); endRemoveRows(); } return true; } bool KisPaletteModel::addGroup(QString groupName) { QModelIndex i = getLastEntryIndex(); beginInsertRows(QModelIndex(), i.row(), i.row()+1); m_colorSet->addGroup(groupName); endInsertRows(); return true; } bool KisPaletteModel::removeRows(int row, int count, const QModelIndex &parent) { Q_ASSERT(!parent.isValid()); int beginRow = qMax(0, row); int endRow = qMin(row + count - 1, (int)m_colorSet->nColors() - 1); beginRemoveRows(parent, beginRow, endRow); // Find the palette entry at row, count, remove from KoColorSet endRemoveRows(); return true; } bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) { return false; } if (action == Qt::IgnoreAction) { return false; } int endRow; int endColumn; if (!parent.isValid()) { if (row < 0) { endRow = indexFromId(m_colorSet->nColors()).row(); endColumn = indexFromId(m_colorSet->nColors()).column(); } else { endRow = qMin(row, indexFromId(m_colorSet->nColors()).row()); endColumn = qMin(column, m_colorSet->columnCount()); } } else { endRow = qMin(parent.row(), rowCount()); endColumn = qMin(parent.column(), columnCount()); } if (data->hasFormat("krita/x-colorsetgroup")) { QByteArray encodedData = data->data("krita/x-colorsetgroup"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { QString groupName; stream >> groupName; QModelIndex index = this->index(endRow, 0); if (index.isValid()) { QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupDroppedOn = QString(); if (!entryList.isEmpty()) { groupDroppedOn = entryList.at(0); } int groupIndex = colorSet()->getGroupNames().indexOf(groupName); beginMoveRows( QModelIndex(), groupIndex, groupIndex, QModelIndex(), endRow); m_colorSet->moveGroup(groupName, groupDroppedOn); m_colorSet->save(); endMoveRows(); ++endRow; } } } else { QByteArray encodedData = data->data("krita/x-colorsetentry"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { KoColorSetEntry entry; QString oldGroupName; int indexInGroup; QString colorXml; stream >> entry.name >> entry.id >> entry.spotColor >> indexInGroup >> oldGroupName >> colorXml; QDomDocument doc; doc.setContent(colorXml); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(); if (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); entry.color = KoColor::fromXML(c, colorDepthId); } QModelIndex index = this->index(endRow, endColumn); if (qVariantValue(index.data(IsHeaderRole))){ endRow+=1; } if (index.isValid()) { /*this is to figure out the row of the old color. * That way we can in turn avoid moverows from complaining the * index is out of bounds when using index. * Makes me wonder if we shouldn't just insert the index of the * old color when requesting the mimetype... */ int i = indexInGroup; if (oldGroupName != QString()) { colorSet()->nColorsGroup(""); //find at which position the group is. int groupIndex = colorSet()->getGroupNames().indexOf(oldGroupName); //add all the groupsizes onto it till we get to our group. for(int g=0; gnColorsGroup(colorSet()->getGroupNames().at(g)); } } QModelIndex indexOld = indexFromId(i); if (action == Qt::MoveAction){ if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) { beginMoveRows(QModelIndex(), indexOld.row(), indexOld.row(), QModelIndex(), qMax(endRow+1,1)); } if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) { beginMoveColumns(QModelIndex(), indexOld.column(), indexOld.column(), QModelIndex(), qMax(endColumn+1,1)); } } else { beginInsertRows(QModelIndex(), endRow, endRow); } QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString entryInGroup = "0"; QString groupName = QString(); if (!entryList.isEmpty()) { groupName = entryList.at(0); entryInGroup = entryList.at(1); } int location = entryInGroup.toInt(); // Insert the entry if (groupName==oldGroupName && qVariantValue(index.data(IsHeaderRole))==true) { groupName=QString(); location=m_colorSet->nColorsGroup(); } m_colorSet->insertBefore(entry, location, groupName); if (groupName==oldGroupName && locationremoveAt(indexInGroup, oldGroupName); } m_colorSet->save(); if (action == Qt::MoveAction){ if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) { endMoveRows(); } if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) { endMoveColumns(); } } else { endInsertRows(); } ++endRow; } } } return true; } QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); QString mimeTypeName = "krita/x-colorsetentry"; //Q_FOREACH(const QModelIndex &index, indexes) { QModelIndex index = indexes.last(); if (index.isValid()) { if (qVariantValue(index.data(IsHeaderRole))==false) { KoColorSetEntry entry = colorSetEntryFromIndex(index); QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupName = QString(); int indexInGroup = 0; if (!entryList.isEmpty()) { groupName = entryList.at(0); QString iig = entryList.at(1); indexInGroup = iig.toInt(); } QDomDocument doc; QDomElement root = doc.createElement("Color"); root.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); doc.appendChild(root); entry.color.toXML(doc, root); stream << entry.name << entry.id << entry.spotColor << indexInGroup << groupName << doc.toString(); } else { mimeTypeName = "krita/x-colorsetgroup"; QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupName = QString(); if (!entryList.isEmpty()) { groupName = entryList.at(0); } stream << groupName; } } mimeData->setData(mimeTypeName, encodedData); return mimeData; } QStringList KisPaletteModel::mimeTypes() const { return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup"; } Qt::DropActions KisPaletteModel::supportedDropActions() const { return Qt::MoveAction; } diff --git a/libs/ui/KisPart.cpp b/libs/ui/KisPart.cpp index 77a4eb4365..bdc3291be5 100644 --- a/libs/ui/KisPart.cpp +++ b/libs/ui/KisPart.cpp @@ -1,491 +1,479 @@ /* This file is part of the KDE project * Copyright (C) 1998-1999 Torben Weis * Copyright (C) 2000-2005 David Faure * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2010-2012 Boudewijn Rempt * Copyright (C) 2011 Inge Wallin * Copyright (C) 2015 Michael Abrahams * * 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 "KisPart.h" #include "KoProgressProxy.h" #include #include #include #include #include #include #include #include #include "KisApplication.h" #include "KisDocument.h" #include "KisView.h" #include "KisViewManager.h" #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisView.h" #include "KisDocument.h" #include "kis_config.h" #include "kis_shape_controller.h" #include "kis_resource_server_provider.h" #include "kis_animation_cache_populator.h" #include "kis_idle_watcher.h" #include "kis_image.h" #include "KisImportExportManager.h" #include "KisDocument.h" #include "KoToolManager.h" #include "KisViewManager.h" #include "kis_script_manager.h" #include "KisOpenPane.h" #include "kis_color_manager.h" #include "kis_debug.h" #include "kis_action.h" #include "kis_action_registry.h" Q_GLOBAL_STATIC(KisPart, s_instance) class Q_DECL_HIDDEN KisPart::Private { public: Private(KisPart *_part) : part(_part) , idleWatcher(2500) , animationCachePopulator(_part) { } ~Private() { } KisPart *part; QList > views; QList > mainWindows; QList > documents; QList scriptActions; KActionCollection *actionCollection{0}; KisIdleWatcher idleWatcher; KisAnimationCachePopulator animationCachePopulator; }; KisPart* KisPart::instance() { return s_instance; } KisPart::KisPart() : d(new Private(this)) { // Preload all the resources in the background Q_UNUSED(KoResourceServerProvider::instance()); Q_UNUSED(KisResourceServerProvider::instance()); Q_UNUSED(KisColorManager::instance()); connect(this, SIGNAL(documentOpened(QString)), this, SLOT(updateIdleWatcherConnections())); connect(this, SIGNAL(documentClosed(QString)), this, SLOT(updateIdleWatcherConnections())); connect(KisActionRegistry::instance(), SIGNAL(shortcutsUpdated()), this, SLOT(updateShortcuts())); connect(&d->idleWatcher, SIGNAL(startedIdleMode()), &d->animationCachePopulator, SLOT(slotRequestRegeneration())); d->animationCachePopulator.slotRequestRegeneration(); } KisPart::~KisPart() { while (!d->documents.isEmpty()) { delete d->documents.takeFirst(); } while (!d->views.isEmpty()) { delete d->views.takeFirst(); } while (!d->mainWindows.isEmpty()) { delete d->mainWindows.takeFirst(); } delete d; } void KisPart::updateIdleWatcherConnections() { QVector images; Q_FOREACH (QPointer document, documents()) { if (document->image()) { images << document->image(); } } d->idleWatcher.setTrackedImages(images); } void KisPart::addDocument(KisDocument *document) { //dbgUI << "Adding document to part list" << document; Q_ASSERT(document); if (!d->documents.contains(document)) { d->documents.append(document); emit documentOpened('/'+objectName()); emit sigDocumentAdded(document); connect(document, SIGNAL(sigSavingFinished()), SLOT(slotDocumentSaved())); } } QList > KisPart::documents() const { return d->documents; } KisDocument *KisPart::createDocument() const { KisDocument *doc = new KisDocument(); return doc; } int KisPart::documentCount() const { return d->documents.size(); } void KisPart::removeDocument(KisDocument *document) { d->documents.removeAll(document); emit documentClosed('/'+objectName()); emit sigDocumentRemoved(document->url().toLocalFile()); document->deleteLater(); } KisMainWindow *KisPart::createMainWindow() { KisMainWindow *mw = new KisMainWindow(); Q_FOREACH(KisAction *action, d->scriptActions) { mw->viewManager()->scriptManager()->addAction(action); } dbgUI <<"mainWindow" << (void*)mw << "added to view" << this; d->mainWindows.append(mw); emit sigWindowAdded(mw); return mw; } KisView *KisPart::createView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent) { // If creating the canvas fails, record this and disable OpenGL next time KisConfig cfg; KConfigGroup grp( KSharedConfig::openConfig(), "crashprevention"); if (grp.readEntry("CreatingCanvas", false)) { cfg.setUseOpenGL(false); } if (cfg.canvasState() == "OPENGL_FAILED") { cfg.setUseOpenGL(false); } grp.writeEntry("CreatingCanvas", true); grp.sync(); QApplication::setOverrideCursor(Qt::WaitCursor); KisView *view = new KisView(document, resourceManager, actionCollection, parent); QApplication::restoreOverrideCursor(); // Record successful canvas creation grp.writeEntry("CreatingCanvas", false); grp.sync(); addView(view); return view; } void KisPart::addView(KisView *view) { if (!view) return; if (!d->views.contains(view)) { d->views.append(view); } emit sigViewAdded(view); } void KisPart::removeView(KisView *view) { if (!view) return; /** * HACK ALERT: we check here explicitly if the document (or main * window), is saving the stuff. If we close the * document *before* the saving is completed, a crash * will happen. */ KIS_ASSERT_RECOVER_RETURN(!view->mainWindow()->hackIsSaving()); emit sigViewRemoved(view); QPointer doc = view->document(); d->views.removeAll(view); if (doc) { bool found = false; Q_FOREACH (QPointer view, d->views) { if (view && view->document() == doc) { found = true; break; } } if (!found) { removeDocument(doc); } } } QList > KisPart::views() const { return d->views; } int KisPart::viewCount(KisDocument *doc) const { if (!doc) { return d->views.count(); } else { int count = 0; Q_FOREACH (QPointer view, d->views) { if (view && view->isVisible() && view->document() == doc) { count++; } } return count; } } void KisPart::slotDocumentSaved() { KisDocument *doc = qobject_cast(sender()); emit sigDocumentSaved(doc->url().toLocalFile()); } void KisPart::removeMainWindow(KisMainWindow *mainWindow) { dbgUI <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this; if (mainWindow) { d->mainWindows.removeAll(mainWindow); } } const QList > &KisPart::mainWindows() const { return d->mainWindows; } int KisPart::mainwindowCount() const { return d->mainWindows.count(); } KisMainWindow *KisPart::currentMainwindow() const { QWidget *widget = qApp->activeWindow(); KisMainWindow *mainWindow = qobject_cast(widget); while (!mainWindow && widget) { widget = widget->parentWidget(); mainWindow = qobject_cast(widget); } if (!mainWindow && mainWindows().size() > 0) { mainWindow = mainWindows().first(); } return mainWindow; } void KisPart::addScriptAction(KisAction *action) { d->scriptActions << action; } KisIdleWatcher* KisPart::idleWatcher() const { return &d->idleWatcher; } KisAnimationCachePopulator* KisPart::cachePopulator() const { return &d->animationCachePopulator; } void KisPart::openExistingFile(const QUrl &url) { - Q_ASSERT(url.isLocalFile()); - qApp->setOverrideCursor(Qt::BusyCursor); - KisDocument *document = createDocument(); - if (!document->openUrl(url)) { - delete document; - return; - } - if (!document->image()) { - delete document; - return; - } - document->setModified(false); - addDocument(document); + // TODO: refactor out this method! KisMainWindow *mw = currentMainwindow(); - mw->addViewAndNotifyLoadingCompleted(document); + KIS_SAFE_ASSERT_RECOVER_RETURN(mw); - qApp->restoreOverrideCursor(); + mw->openDocument(url, KisMainWindow::None); } void KisPart::updateShortcuts() { // Update any non-UI actionCollections. That includes: // - Shortcuts called inside of tools // - Perhaps other things? KoToolManager::instance()->updateToolShortcuts(); // Now update the UI actions. Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { KActionCollection *ac = mainWindow->actionCollection(); ac->updateShortcuts(); // Loop through mainWindow->actionCollections() to modify tooltips // so that they list shortcuts at the end in parentheses Q_FOREACH ( QAction* action, ac->actions()) { // Remove any existing suffixes from the tooltips. // Note this regexp starts with a space, e.g. " (Ctrl-a)" QString strippedTooltip = action->toolTip().remove(QRegExp("\\s\\(.*\\)")); // Now update the tooltips with the new shortcut info. if(action->shortcut() == QKeySequence(0)) action->setToolTip(strippedTooltip); else action->setToolTip( strippedTooltip + " (" + action->shortcut().toString() + ")"); } } } void KisPart::openTemplate(const QUrl &url) { qApp->setOverrideCursor(Qt::BusyCursor); KisDocument *document = createDocument(); bool ok = document->loadNativeFormat(url.toLocalFile()); document->setModified(false); document->undoStack()->clear(); if (ok) { QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); // in case this is a open document template remove the -template from the end mimeType.remove( QRegExp( "-template$" ) ); document->setMimeTypeAfterLoading(mimeType); document->resetURL(); } else { if (document->errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1", document->localFilePath())); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1\nReason: %2", document->localFilePath(), document->errorMessage())); } delete document; return; } addDocument(document); KisMainWindow *mw = currentMainwindow(); mw->addViewAndNotifyLoadingCompleted(document); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } qApp->restoreOverrideCursor(); } void KisPart::addRecentURLToAllMainWindows(QUrl url) { // Add to recent actions list in our mainWindows Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { mainWindow->addRecentURL(url); } } void KisPart::startCustomDocument(KisDocument* doc) { addDocument(doc); KisMainWindow *mw = currentMainwindow(); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } mw->addViewAndNotifyLoadingCompleted(doc); } KisInputManager* KisPart::currentInputManager() { KisMainWindow *mw = currentMainwindow(); KisViewManager *manager = mw ? mw->viewManager() : 0; return manager ? manager->inputManager() : 0; } diff --git a/libs/ui/KisSaveGroupVisitor.cpp b/libs/ui/KisSaveGroupVisitor.cpp index b23e6d1f96..ee3f21cd2e 100644 --- a/libs/ui/KisSaveGroupVisitor.cpp +++ b/libs/ui/KisSaveGroupVisitor.cpp @@ -1,138 +1,139 @@ /* * Copyright (C) 2016 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 "KisSaveGroupVisitor.h" #include #include #include #include KisSaveGroupVisitor::KisSaveGroupVisitor(KisImageWSP image, bool saveInvisible, bool saveTopLevelOnly, const QString &path, const QString &baseName, const QString &extension, const QString &mimeFilter) : m_image(image) , m_saveInvisible(saveInvisible) , m_saveTopLevelOnly(saveTopLevelOnly) , m_path(path) , m_baseName(baseName) , m_extension(extension) , m_mimeFilter(mimeFilter) { } KisSaveGroupVisitor::~KisSaveGroupVisitor() { } bool KisSaveGroupVisitor::visit(KisNode* ) { return true; } bool KisSaveGroupVisitor::visit(KisPaintLayer *) { return true; } bool KisSaveGroupVisitor::visit(KisAdjustmentLayer *) { return true; } bool KisSaveGroupVisitor::visit(KisExternalLayer *) { return true; } bool KisSaveGroupVisitor::visit(KisCloneLayer *) { return true; } bool KisSaveGroupVisitor::visit(KisFilterMask *) { return true; } bool KisSaveGroupVisitor::visit(KisTransformMask *) { return true; } bool KisSaveGroupVisitor::visit(KisTransparencyMask *) { return true; } bool KisSaveGroupVisitor::visit(KisGeneratorLayer * ) { return true; } bool KisSaveGroupVisitor::visit(KisSelectionMask* ) { return true; } bool KisSaveGroupVisitor::visit(KisColorizeMask* ) { return true; } bool KisSaveGroupVisitor::visit(KisGroupLayer *layer) { if (layer == m_image->rootLayer()) { KisLayerSP child = qobject_cast(layer->firstChild().data()); while (child) { child->accept(*this); child = qobject_cast(child->nextSibling().data()); } } else if (layer->visible() || m_saveInvisible) { QRect r = m_image->bounds(); KisDocument *exportDocument = KisPart::instance()->createDocument(); KisImageSP dst = new KisImage(exportDocument->createUndoStore(), r.width(), r.height(), m_image->colorSpace(), layer->name()); dst->setResolution(m_image->xRes(), m_image->yRes()); exportDocument->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", layer->opacity()); KisPainter gc(paintLayer->paintDevice()); gc.bitBlt(QPoint(0, 0), layer->projection(), r); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->refreshGraph(); - exportDocument->setOutputMimeType(m_mimeFilter.toLatin1()); - exportDocument->setFileBatchMode(true); + QString path = m_path + "/" + m_baseName + "_" + layer->name().replace(' ', '_') + '.' + m_extension; QUrl url = QUrl::fromLocalFile(path); - exportDocument->exportDocument(url); + + exportDocument->setFileBatchMode(true); + exportDocument->exportDocumentSync(url, m_mimeFilter.toLatin1()); if (!m_saveTopLevelOnly) { KisGroupLayerSP child = dynamic_cast(layer->firstChild().data()); while (child) { child->accept(*this); child = dynamic_cast(child->nextSibling().data()); } } delete exportDocument; } return true; } diff --git a/libs/ui/KisTemplateCreateDia.cpp b/libs/ui/KisTemplateCreateDia.cpp index b7c167d05c..81084a9af2 100644 --- a/libs/ui/KisTemplateCreateDia.cpp +++ b/libs/ui/KisTemplateCreateDia.cpp @@ -1,518 +1,518 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer 2000 Werner Trobin Copyright (C) 2004 Nicolas GOUTTE This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ODF thumbnail extent static const int thumbnailExtent = 128; class KisTemplateCreateDiaPrivate { public: KisTemplateCreateDiaPrivate(const QString &templatesResourcePath, const QString &filePath, const QPixmap &thumbnail) : m_tree(templatesResourcePath, true) , m_filePath(filePath) , m_thumbnail(thumbnail) { } KisTemplateTree m_tree; QLineEdit *m_name; QRadioButton *m_default; QRadioButton *m_custom; QPushButton *m_select; QLabel *m_preview; QString m_customFile; QPixmap m_customPixmap; QTreeWidget *m_groups; QPushButton *m_add; QPushButton *m_remove; QCheckBox *m_defaultTemplate; QString m_filePath; QPixmap m_thumbnail; bool m_changed; }; /**************************************************************************** * * Class: KisTemplateCreateDia * ****************************************************************************/ KisTemplateCreateDia::KisTemplateCreateDia(const QString &templatesResourcePath, const QString &filePath, const QPixmap &thumbnail, QWidget *parent) : KoDialog(parent) , d(new KisTemplateCreateDiaPrivate(templatesResourcePath, filePath, thumbnail)) { setButtons( KoDialog::Ok|KoDialog::Cancel ); setDefaultButton( KoDialog::Ok ); setCaption( i18n( "Create Template" ) ); setModal( true ); setObjectName( "template create dia" ); QWidget *mainwidget = mainWidget(); QHBoxLayout *mbox=new QHBoxLayout( mainwidget ); QVBoxLayout* leftbox = new QVBoxLayout(); mbox->addLayout( leftbox ); QLabel *label=new QLabel(i18nc("Template name", "Name:"), mainwidget); QHBoxLayout *namefield=new QHBoxLayout(); leftbox->addLayout( namefield ); namefield->addWidget(label); d->m_name=new QLineEdit(mainwidget); d->m_name->setFocus(); connect(d->m_name, SIGNAL(textChanged(const QString &)), this, SLOT(slotNameChanged(const QString &))); namefield->addWidget(d->m_name); label=new QLabel(i18n("Group:"), mainwidget); leftbox->addWidget(label); d->m_groups = new QTreeWidget(mainwidget); leftbox->addWidget(d->m_groups); d->m_groups->setColumnCount(1); d->m_groups->setHeaderHidden(true); d->m_groups->setRootIsDecorated(true); d->m_groups->setSortingEnabled(true); fillGroupTree(); d->m_groups->sortItems(0, Qt::AscendingOrder); QHBoxLayout *bbox=new QHBoxLayout(); leftbox->addLayout( bbox ); d->m_add=new QPushButton(i18n("&Add Group..."), mainwidget); connect(d->m_add, SIGNAL(clicked()), this, SLOT(slotAddGroup())); bbox->addWidget(d->m_add); d->m_remove=new QPushButton(i18n("&Remove"), mainwidget); connect(d->m_remove, SIGNAL(clicked()), this, SLOT(slotRemove())); bbox->addWidget(d->m_remove); QVBoxLayout *rightbox=new QVBoxLayout(); mbox->addLayout( rightbox ); QGroupBox *pixbox = new QGroupBox(i18n("Picture"), mainwidget); rightbox->addWidget(pixbox); QVBoxLayout *pixlayout=new QVBoxLayout(pixbox ); d->m_default=new QRadioButton(i18n("&Preview"), pixbox); d->m_default->setChecked(true); connect(d->m_default, SIGNAL(clicked()), this, SLOT(slotDefault())); pixlayout->addWidget(d->m_default); QHBoxLayout *custombox=new QHBoxLayout(); d->m_custom=new QRadioButton(i18n("Custom:"), pixbox); d->m_custom->setChecked(false); connect(d->m_custom, SIGNAL(clicked()), this, SLOT(slotCustom())); custombox->addWidget(d->m_custom); d->m_select=new QPushButton(i18n("&Select..."), pixbox); connect(d->m_select, SIGNAL(clicked()), this, SLOT(slotSelect())); custombox->addWidget(d->m_select); custombox->addStretch(1); pixlayout->addLayout(custombox); d->m_preview=new QLabel(pixbox); // setPixmap() -> auto resize? pixlayout->addWidget(d->m_preview, 0, Qt::AlignCenter); pixlayout->addStretch(1); d->m_defaultTemplate = new QCheckBox( i18n("Use the new template as default"), mainwidget ); d->m_defaultTemplate->setChecked( true ); d->m_defaultTemplate->setVisible( false ); d->m_defaultTemplate->setToolTip(i18n("Use the new template every time Krita starts")); rightbox->addWidget( d->m_defaultTemplate ); enableButtonOk(false); d->m_changed=false; updatePixmap(); connect(d->m_groups, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectionChanged())); d->m_remove->setEnabled(d->m_groups->currentItem()); connect(this, SIGNAL(okClicked()), this, SLOT(slotOk())); } KisTemplateCreateDia::~KisTemplateCreateDia() { delete d; } void KisTemplateCreateDia::slotSelectionChanged() { const QTreeWidgetItem* item = d->m_groups->currentItem(); d->m_remove->setEnabled( item ); if ( ! item ) return; if ( item->parent() != 0 ) { d->m_name->setText( item->text( 0 ) ); } } void KisTemplateCreateDia::createTemplate(const QString &templatesResourcePath, const char *suffix, KisDocument *document, QWidget *parent) { Q_UNUSED(suffix); QString fileName; { QTemporaryFile tempFile; if (!tempFile.open()) { qWarning("Creation of temporary file to store template failed."); return; } fileName = tempFile.fileName(); } - bool retval = document->exportDocument(QUrl::fromLocalFile(fileName)); + bool retval = document->exportDocumentSync(QUrl::fromLocalFile(fileName), document->mimeType()); if (!retval) { qWarning("Could not save template"); return; } const QPixmap thumbnail = document->generatePreview(QSize(thumbnailExtent, thumbnailExtent)); KisTemplateCreateDia *dia = new KisTemplateCreateDia(templatesResourcePath, fileName, thumbnail, parent); dia->exec(); delete dia; QDir d; d.remove(fileName); } static void saveAsQuadraticPng(const QPixmap &pixmap, const QString &fileName) { QImage icon = pixmap.toImage(); icon = icon.convertToFormat(QImage::Format_ARGB32); const int iconExtent = qMax(icon.width(), icon.height()); icon = icon.copy((icon.width() - iconExtent) / 2, (icon.height() - iconExtent) / 2, iconExtent, iconExtent); icon.save(fileName, "PNG"); } void KisTemplateCreateDia::slotOk() { // get the current item, if there is one... QTreeWidgetItem *item = d->m_groups->currentItem(); if(!item) item = d->m_groups->topLevelItem(0); if(!item) { // safe :) d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } // is it a group or a template? anyway - get the group :) if(item->parent() != 0) item=item->parent(); if(!item) { // *very* safe :P d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } KisTemplateGroup *group=d->m_tree.find(item->text(0)); if(!group) { // even safer d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } if(d->m_name->text().isEmpty()) { d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } // copy the tmp file and the picture the app provides QString dir = KoResourcePaths::saveLocation("data", d->m_tree.templatesResourcePath()); dir += group->name(); QString templateDir = dir+"/.source/"; QString iconDir = dir+"/.icon/"; QString file = KisTemplates::trimmed(d->m_name->text()); QString tmpIcon = ".icon/"+file; tmpIcon += ".png"; QString icon=iconDir+file; icon += ".png"; QString ext = ".kra"; QString dest = templateDir + file + ext; if (QFile::exists(dest)) { do { file = file.prepend( '_' ); dest = templateDir + file + ext; tmpIcon=".icon/" + file + ".png"; icon=iconDir + file + ".png"; } while (QFile(dest).exists()); } bool ignore = false; KisTemplate *t = new KisTemplate(d->m_name->text(), QString(), ".source/"+ file + ext, tmpIcon, "", "", false, true); if (!group->add(t)) { KisTemplate *existingTemplate=group->find(d->m_name->text()); if (existingTemplate && !existingTemplate->isHidden()) { if (QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Do you really want to overwrite the existing '%1' template?", existingTemplate->name()), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { group->add(t, true); } else { delete t; return; } } else { ignore = true; } } QDir path; if (!path.mkpath(templateDir) || !path.mkpath(iconDir)) { d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } QString orig; orig = d->m_filePath; // don't overwrite the hidden template file with a new non-hidden one if (!ignore) { if (!QFile::copy(d->m_filePath, dest)) { qWarning() << "Could not copy" << d->m_filePath << "to" << dest; } // save the picture as icon if(d->m_default->isChecked() && !d->m_thumbnail.isNull()) { saveAsQuadraticPng(d->m_thumbnail, icon); } else if(!d->m_customPixmap.isNull()) { saveAsQuadraticPng(d->m_customPixmap, icon); } else { warnUI << "Could not save the preview picture!"; } } // if there's a .directory file, we copy this one, too bool ready=false; QStringList tmp=group->dirs(); for(QStringList::ConstIterator it=tmp.constBegin(); it!=tmp.constEnd() && !ready; ++it) { if((*it).contains(dir)==0) { orig = (*it) + ".directory"; // Check if we can read the file if (QFile(orig).exists()) { dest = dir + "/.directory"; // We copy the file with overwrite if (!QFile(orig).copy(dest)) { warnKrita << "Failed to copy from" << orig << "to" << dest; } ready = true; } } } d->m_tree.writeTemplateTree(); if ( d->m_defaultTemplate->isChecked() ) { KConfigGroup grp( KSharedConfig::openConfig(), "TemplateChooserDialog"); grp.writeEntry( "LastReturnType", "Template" ); grp.writePathEntry( "FullTemplateName", dir + '/' + t->file() ); grp.writePathEntry( "AlwaysUseTemplate", dir + '/' + t->file() ); } } void KisTemplateCreateDia::slotDefault() { d->m_default->setChecked(true); d->m_custom->setChecked(false); updatePixmap(); } void KisTemplateCreateDia::slotCustom() { d->m_default->setChecked(false); d->m_custom->setChecked(true); if(d->m_customFile.isEmpty()) slotSelect(); else updatePixmap(); } void KisTemplateCreateDia::slotSelect() { d->m_default->setChecked(false); d->m_custom->setChecked(true); // QT5TODO // QString name = KIconDialog::getIcon(); // if( name.isEmpty() ) { // if(d->m_customFile.isEmpty()) { // d->m_default->setChecked(true); // d->m_custom->setChecked(false); // } // return; // } // const QString path = KIconLoader::global()->iconPath(name, -thumbnailExtent); d->m_customFile = QString();// path; d->m_customPixmap = QPixmap(); updatePixmap(); } void KisTemplateCreateDia::slotNameChanged(const QString &name) { if( ( name.trimmed().isEmpty() || !d->m_groups->topLevelItem(0) ) && !d->m_changed ) enableButtonOk(false); else enableButtonOk(true); } void KisTemplateCreateDia::slotAddGroup() { const QString name = QInputDialog::getText(this, i18n("Add Group"), i18n("Enter group name:")); KisTemplateGroup *group = d->m_tree.find(name); if (group && !group->isHidden()) { QMessageBox::information( this, i18n("This name is already used."), i18n("Add Group") ); return; } QString dir = KoResourcePaths::saveLocation("data", d->m_tree.templatesResourcePath()); dir+=name; KisTemplateGroup *newGroup=new KisTemplateGroup(name, dir, 0, true); d->m_tree.add(newGroup); QTreeWidgetItem *item = new QTreeWidgetItem(d->m_groups, QStringList() << name); d->m_groups->setCurrentItem(item); d->m_groups->sortItems(0, Qt::AscendingOrder); d->m_name->setFocus(); enableButtonOk(true); d->m_changed=true; } void KisTemplateCreateDia::slotRemove() { QTreeWidgetItem *item = d->m_groups->currentItem(); if(!item) return; QString what; QString removed; if (item->parent() == 0) { what = i18n("Do you really want to remove that group?"); removed = i18nc("@title:window", "Remove Group"); } else { what = i18n("Do you really want to remove that template?"); removed = i18nc("@title:window", "Remove Template"); } if (QMessageBox::warning(this, removed, what, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox:: No) { d->m_name->setFocus(); return; } if(item->parent() == 0) { KisTemplateGroup *group=d->m_tree.find(item->text(0)); if(group) group->setHidden(true); } else { bool done=false; QList groups = d->m_tree.groups(); QList::const_iterator it = groups.constBegin(); for(; it != groups.constEnd() && !done; ++it) { KisTemplate *t = (*it)->find(item->text(0)); if(t) { t->setHidden(true); done=true; } } } delete item; item=0; enableButtonOk(true); d->m_name->setFocus(); d->m_changed=true; } void KisTemplateCreateDia::updatePixmap() { if(d->m_default->isChecked() && !d->m_thumbnail.isNull()) d->m_preview->setPixmap(d->m_thumbnail); else if(d->m_custom->isChecked() && !d->m_customFile.isEmpty()) { if(d->m_customPixmap.isNull()) { dbgUI <<"Trying to load picture" << d->m_customFile; // use the code in KisTemplate to load the image... hacky, I know :) KisTemplate t("foo", "bar", QString(), d->m_customFile); d->m_customPixmap=t.loadPicture(); } else warnUI << "Trying to load picture"; if(!d->m_customPixmap.isNull()) d->m_preview->setPixmap(d->m_customPixmap); else d->m_preview->setText(i18n("Could not load picture.")); } else d->m_preview->setText(i18n("No picture available.")); } void KisTemplateCreateDia::fillGroupTree() { Q_FOREACH (KisTemplateGroup *group, d->m_tree.groups()) { if(group->isHidden()) continue; QTreeWidgetItem *groupItem=new QTreeWidgetItem(d->m_groups, QStringList() << group->name()); Q_FOREACH (KisTemplate *t, group->templates()) { if(t->isHidden()) continue; (void)new QTreeWidgetItem(groupItem, QStringList() << t->name()); } } } diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp index 2db67e1548..b9ef1ff457 100644 --- a/libs/ui/KisView.cpp +++ b/libs/ui/KisView.cpp @@ -1,967 +1,960 @@ /* * 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 "KisView.h" #include "KisView_p.h" #include #include #include #include "KoDocumentInfo.h" #include "KoPageLayout.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 "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_config.h" #include "KisDocument.h" #include "kis_image_manager.h" #include "KisMainWindow.h" #include "kis_mimedata.h" #include "kis_mirror_axis.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_shape_controller.h" #include "kis_tool_freehand.h" #include "KisUndoStackAction.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" -#include "kis_composite_progress_proxy.h" #include "kis_statusbar.h" #include "kis_painting_assistants_decoration.h" -#include "kis_progress_widget.h" #include "kis_signal_compressor.h" #include "kis_filter_manager.h" #include "kis_file_layer.h" #include "krita_utils.h" #include "input/kis_input_manager.h" #include "KisRemoteFileFetcher.h" //static QString KisView::newObjectName() { static int s_viewIFNumber = 0; QString name; name.setNum(s_viewIFNumber++); name.prepend("view_"); return name; } bool KisView::s_firstView = true; class Q_DECL_HIDDEN KisView::Private { public: Private(KisView *_q, KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection) : actionCollection(actionCollection) , viewConverter() , canvasController(_q, actionCollection) , canvas(&viewConverter, resourceManager, _q, document->shapeController()) , zoomManager(_q, &this->viewConverter, &this->canvasController) , paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q)) , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE) { } KisUndoStackAction *undo = 0; KisUndoStackAction *redo = 0; bool inOperation; //in the middle of an operation (no screen refreshing)? QPointer document; // our KisDocument QWidget *tempActiveWidget = 0; /** * Signals the document has been deleted. Can't use document==0 since this * only happens in ~QObject, and views get deleted by ~KisDocument. * XXX: either provide a better justification to do things this way, or * rework the mechanism. */ bool documentDeleted = false; KActionCollection* actionCollection; KisCoordinatesConverter viewConverter; KisCanvasController canvasController; KisCanvas2 canvas; KisZoomManager zoomManager; KisViewManager *viewManager = 0; KisNodeSP currentNode; KisPaintingAssistantsDecorationSP paintingAssistantsDecoration; bool isCurrent = false; bool showFloatingMessage = false; QPointer savedFloatingMessage; KisSignalCompressor floatingMessageCompressor; bool softProofing = false; bool gamutCheck = false; // Hmm sorry for polluting the private class with such a big inner class. // At the beginning it was a little struct :) class StatusBarItem { public: StatusBarItem(QWidget * widget, int stretch, bool permanent) : m_widget(widget), m_stretch(stretch), m_permanent(permanent), m_connected(false), m_hidden(false) {} bool operator==(const StatusBarItem& rhs) { return m_widget == rhs.m_widget; } bool operator!=(const StatusBarItem& rhs) { return m_widget != rhs.m_widget; } QWidget * widget() const { return m_widget; } void ensureItemShown(QStatusBar * sb) { Q_ASSERT(m_widget); if (!m_connected) { if (m_permanent) sb->addPermanentWidget(m_widget, m_stretch); else sb->addWidget(m_widget, m_stretch); if(!m_hidden) m_widget->show(); m_connected = true; } } void ensureItemHidden(QStatusBar * sb) { if (m_connected) { m_hidden = m_widget->isHidden(); sb->removeWidget(m_widget); m_widget->hide(); m_connected = false; } } private: QWidget * m_widget = 0; int m_stretch; bool m_permanent; bool m_connected = false; bool m_hidden = false; }; }; KisView::KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent) : QWidget(parent) , d(new Private(this, document, resourceManager, actionCollection)) { Q_ASSERT(document); connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool))); setObjectName(newObjectName()); d->document = document; setFocusPolicy(Qt::StrongFocus); d->undo = new KisUndoStackAction(d->document->undoStack(), KisUndoStackAction::UNDO); d->redo = new KisUndoStackAction(d->document->undoStack(), KisUndoStackAction::RED0); QStatusBar * sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror - connect(d->document, SIGNAL(statusBarMessage(const QString&)), - this, SLOT(slotActionStatusText(const QString&))); + connect(d->document, SIGNAL(statusBarMessage(const QString&, int)), + this, SLOT(slotActionStatusText(const QString&, int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } d->canvas.setup(); KisConfig cfg; d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setDrawShadow(false); d->canvasController.setCanvasMode(KoCanvasController::Infinite); d->canvasController.setVastScrolling(cfg.vastScrolling()); d->canvasController.setCanvas(&d->canvas); d->zoomManager.setup(d->actionCollection); connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged())); setAcceptDrops(true); connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished())); connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished())); d->canvas.addDecoration(d->paintingAssistantsDecoration); d->paintingAssistantsDecoration->setVisible(true); d->showFloatingMessage = cfg.showCanvasMessages(); } KisView::~KisView() { if (d->viewManager) { - KoProgressProxy *proxy = d->viewManager->statusBar()->progress()->progressProxy(); - KIS_ASSERT_RECOVER_NOOP(proxy); - image()->compositeProgressProxy()->removeProxy(proxy); - if (d->viewManager->filterManager()->isStrokeRunning()) { d->viewManager->filterManager()->cancel(); } + + d->viewManager->mainWindow()->notifyChildViewDestroyed(this); } KoToolManager::instance()->removeCanvasController(&d->canvasController); KisPart::instance()->removeView(this); delete d; } void KisView::notifyCurrentStateChanged(bool isCurrent) { d->isCurrent = isCurrent; if (!d->isCurrent && d->savedFloatingMessage) { d->savedFloatingMessage->removeMessage(); } KisInputManager *inputManager = globalInputManager(); if (d->isCurrent) { inputManager->attachPriorityEventFilter(&d->canvasController); } else { inputManager->detachPriorityEventFilter(&d->canvasController); } } void KisView::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisView::showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->viewManager) return; if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) { if (d->savedFloatingMessage) { d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment); } else { d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment); d->savedFloatingMessage->setShowOverParent(true); d->savedFloatingMessage->setIcon(icon); connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage())); d->floatingMessageCompressor.start(); } } } bool KisView::canvasIsMirrored() const { return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored(); } void KisView::setViewManager(KisViewManager *view) { d->viewManager = view; KoToolManager::instance()->addController(&d->canvasController); KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController); dynamic_cast(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas); if (resourceProvider()) { resourceProvider()->slotImageSizeChanged(); } if (d->viewManager && d->viewManager->nodeManager()) { d->viewManager->nodeManager()->nodesUpdated(); } connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), this, SLOT(slotImageSizeChanged(const QPointF&, const QPointF&))); connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged())); // executed in a context of an image thread connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), SLOT(slotImageNodeAdded(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)), SLOT(slotContinueAddNode(KisNodeSP)), Qt::AutoConnection); // executed in a context of an image thread connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(slotImageNodeRemoved(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)), SLOT(slotContinueRemoveNode(KisNodeSP)), Qt::AutoConnection); - /* - * WARNING: Currently we access the global progress bar in two ways: - * connecting to composite progress proxy (strokes) and creating - * progress updaters. The latter way should be deprecated in favour - * of displaying the status of the global strokes queue - */ - image()->compositeProgressProxy()->addProxy(d->viewManager->statusBar()->progress()->progressProxy()); - connect(d->viewManager->statusBar()->progress(), SIGNAL(sigCancellationRequested()), image(), SLOT(requestStrokeCancellation())); - d->viewManager->updateGUI(); KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } KisViewManager* KisView::viewManager() const { return d->viewManager; } void KisView::slotImageNodeAdded(KisNodeSP node) { emit sigContinueAddNode(node); } void KisView::slotContinueAddNode(KisNodeSP newActiveNode) { /** * When deleting the last layer, root node got selected. We should * fix it when the first layer is added back. * * Here we basically reimplement what Qt's view/model do. But * since they are not connected, we should do it manually. */ if (!d->isCurrent && (!d->currentNode || !d->currentNode->parent())) { d->currentNode = newActiveNode; } } void KisView::slotImageNodeRemoved(KisNodeSP node) { emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node)); } void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode) { if (!d->isCurrent) { d->currentNode = newActiveNode; } } QAction *KisView::undoAction() const { return d->undo; } QAction *KisView::redoAction() const { return d->redo; } KoZoomController *KisView::zoomController() const { return d->zoomManager.zoomController(); } KisZoomManager *KisView::zoomManager() const { return &d->zoomManager; } KisCanvasController *KisView::canvasController() const { return &d->canvasController; } KisCanvasResourceProvider *KisView::resourceProvider() const { if (d->viewManager) { return d->viewManager->resourceProvider(); } return 0; } KisInputManager* KisView::globalInputManager() const { return d->viewManager ? d->viewManager->inputManager() : 0; } KisCanvas2 *KisView::canvasBase() const { return &d->canvas; } KisImageWSP KisView::image() const { if (d->document) { return d->document->image(); } return 0; } KisCoordinatesConverter *KisView::viewConverter() const { return &d->viewConverter; } void KisView::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasImage() || event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node")) { event->accept(); // activate view if it should accept the drop this->setFocus(); } else { event->ignore(); } } void KisView::dropEvent(QDropEvent *event) { KisImageWSP kisimage = image(); Q_ASSERT(kisimage); QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint(); QRect imageBounds = kisimage->bounds(); QPoint pasteCenter; bool forceRecenter; if (event->keyboardModifiers() & Qt::ShiftModifier && imageBounds.contains(cursorPos)) { pasteCenter = cursorPos; forceRecenter = true; } else { pasteCenter = imageBounds.center(); forceRecenter = false; } if (event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasImage()) { KisShapeController *kritaShapeController = dynamic_cast(d->document->shapeController()); QList nodes = KisMimeData::loadNodes(event->mimeData(), imageBounds, pasteCenter, forceRecenter, kisimage, kritaShapeController); Q_FOREACH (KisNodeSP node, nodes) { if (node) { KisNodeCommandsAdapter adapter(viewManager()); if (!viewManager()->nodeManager()->activeLayer()) { adapter.addNode(node, kisimage->rootLayer() , 0); } else { adapter.addNode(node, viewManager()->nodeManager()->activeLayer()->parent(), viewManager()->nodeManager()->activeLayer()); } } } } else if (event->mimeData()->hasUrls()) { QList urls = event->mimeData()->urls(); if (urls.length() > 0) { QMenu popup; popup.setObjectName("drop_popup"); QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup); QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup); QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup); QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup); QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup); QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup); QAction *cancel = new QAction(i18n("Cancel"), &popup); popup.addAction(insertAsNewLayer); popup.addAction(insertAsNewFileLayer); popup.addAction(openInNewDocument); popup.addAction(insertManyLayers); popup.addAction(insertManyFileLayers); popup.addAction(openManyDocuments); insertAsNewLayer->setEnabled(image() && urls.count() == 1); insertAsNewFileLayer->setEnabled(image() && urls.count() == 1); openInNewDocument->setEnabled(urls.count() == 1); insertManyLayers->setEnabled(image() && urls.count() > 1); insertManyFileLayers->setEnabled(image() && urls.count() > 1); openManyDocuments->setEnabled(urls.count() > 1); popup.addSeparator(); popup.addAction(cancel); QAction *action = popup.exec(QCursor::pos()); if (action != 0 && action != cancel) { QTemporaryFile *tmp = 0; for (QUrl url : urls) { if (!url.isLocalFile()) { // download the file and substitute the url KisRemoteFileFetcher fetcher; tmp = new QTemporaryFile(); tmp->setAutoRemove(true); if (!fetcher.fetchFile(url, tmp)) { qDebug() << "Fetching" << url << "failed"; continue; } url = url.fromLocalFile(tmp->fileName()); } if (url.isLocalFile()) { if (action == insertAsNewLayer || action == insertManyLayers) { d->viewManager->imageManager()->importImage(url); activateWindow(); } else if (action == insertAsNewFileLayer || action == insertManyFileLayers) { KisNodeCommandsAdapter adapter(viewManager()); KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(), KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8); adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode()); } else { Q_ASSERT(action == openInNewDocument || action == openManyDocuments); if (mainWindow()) { - mainWindow()->openDocument(url); + mainWindow()->openDocument(url, KisMainWindow::None); } } } delete tmp; tmp = 0; } } } } } KisDocument *KisView::document() const { return d->document; } void KisView::setDocument(KisDocument *document) { d->document->disconnect(this); d->document = document; QStatusBar *sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror - connect(d->document, SIGNAL(statusBarMessage(const QString&)), - this, SLOT(slotActionStatusText(const QString&))); + connect(d->document, SIGNAL(statusBarMessage(const QString&, int)), + this, SLOT(slotActionStatusText(const QString&, int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } } void KisView::setDocumentDeleted() { d->documentDeleted = true; } QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent) { Q_UNUSED(parent); QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this); printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage()); printDialog->setEnabledOptions(printJob->printDialogOptions()); return printDialog; } KisMainWindow * KisView::mainWindow() const { return dynamic_cast(window()); } QStatusBar * KisView::statusBar() const { KisMainWindow *mw = mainWindow(); return mw ? mw->statusBar() : 0; } -void KisView::slotActionStatusText(const QString &text) +void KisView::slotActionStatusText(const QString &text, int timeout) { QStatusBar *sb = statusBar(); if (sb) - sb->showMessage(text); + sb->showMessage(text, timeout); } void KisView::slotClearStatusText() { QStatusBar *sb = statusBar(); if (sb) sb->clearMessage(); } QList KisView::createChangeUnitActions(bool addPixelUnit) { UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this); return unitActions->actions(); } void KisView::closeEvent(QCloseEvent *event) { // Check whether we're the last view int viewCount = KisPart::instance()->viewCount(document()); if (viewCount > 1) { // there are others still, so don't bother the user event->accept(); return; } if (queryClose()) { - d->viewManager->removeStatusBarItem(zoomManager()->zoomActionWidget()); + d->viewManager->statusBar()->setView(0); event->accept(); return; } event->ignore(); } bool KisView::queryClose() { if (!document()) return true; - if (document()->isInSaving()) { - viewManager()->showFloatingMessage( - i18n("Cannot close the document while saving is in progress"), - KisIconUtils::loadIcon("object-locked"), 1500 /* ms */); - return false; + if (document()->isSaving()) { + document()->waitForSavingToComplete(); } if (document()->isModified()) { QString name; if (document()->documentInfo()) { name = document()->documentInfo()->aboutInfo("title"); } if (name.isEmpty()) name = document()->url().fileName(); if (name.isEmpty()) name = i18n("Untitled"); int res = QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("

The document '%1' has been modified.

Do you want to save it?

", name), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : { - bool isNative = (document()->outputMimeType() == document()->nativeFormatMimeType()); - if (!viewManager()->mainWindow()->saveDocument(document(), !isNative)) + bool isNative = (document()->mimeType() == document()->nativeFormatMimeType()); + if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false)) return false; break; } case QMessageBox::No : { KisImageSP image = document()->image(); image->requestStrokeCancellation(); viewManager()->blockUntilOperationsFinishedForced(image); document()->removeAutoSaveFiles(); document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; } default : // case QMessageBox::Cancel : return false; } } return true; } void KisView::resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint, const QPointF &newImageStillPoint) { const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter(); QPointF oldPreferredCenter = d->canvasController.preferredCenter(); /** * Calculating the still point in old coordinates depending on the * parameters given */ QPointF oldStillPoint; if (changeCentering) { oldStillPoint = converter->imageToWidget(oldImageStillPoint) + converter->documentOffset(); } else { QSize oldDocumentSize = d->canvasController.documentSize(); oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height()); } /** * Updating the document size */ QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes()); KoZoomController *zc = d->zoomManager.zoomController(); zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom()); zc->setPageSize(size); zc->setDocumentSize(size, true); /** * Calculating the still point in new coordinates depending on the * parameters given */ QPointF newStillPoint; if (changeCentering) { newStillPoint = converter->imageToWidget(newImageStillPoint) + converter->documentOffset(); } else { QSize newDocumentSize = d->canvasController.documentSize(); newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height()); } d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint); } +void KisView::syncLastActiveNodeToDocument() +{ + KisDocument *doc = document(); + if (doc) { + doc->setPreActivatedNode(d->currentNode); + } +} + void KisView::setCurrentNode(KisNodeSP node) { d->currentNode = node; d->canvas.slotTrySwitchShapeManager(); + + syncLastActiveNodeToDocument(); } KisNodeSP KisView::currentNode() const { return d->currentNode; } KisLayerSP KisView::currentLayer() const { KisNodeSP node; KisMaskSP mask = currentMask(); if (mask) { node = mask->parent(); } else { node = d->currentNode; } return qobject_cast(node.data()); } KisMaskSP KisView::currentMask() const { return dynamic_cast(d->currentNode.data()); } KisSelectionSP KisView::selection() { KisLayerSP layer = currentLayer(); if (layer) return layer->selection(); // falls through to the global // selection, or 0 in the end if (image()) { return image()->globalSelection(); } return 0; } void KisView::slotSoftProofing(bool softProofing) { d->softProofing = softProofing; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Soft Proofing doesn't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (softProofing){ message = i18n("Soft Proofing turned on."); } else { message = i18n("Soft Proofing turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotSoftProofing(softProofing); } void KisView::slotGamutCheck(bool gamutCheck) { d->gamutCheck = gamutCheck; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Gamut Warnings don't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (gamutCheck){ message = i18n("Gamut Warnings turned on."); if (!d->softProofing){ message += "\n "+i18n("But Soft Proofing is still off."); } } else { message = i18n("Gamut Warnings turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotGamutCheck(gamutCheck); } bool KisView::softProofing() { return d->softProofing; } bool KisView::gamutCheck() { return d->gamutCheck; } void KisView::slotLoadingFinished() { if (!document()) return; /** * Cold-start of image size/resolution signals */ slotImageResolutionChanged(); if (image()->locked()) { // If this is the first view on the image, the image will have been locked // so unlock it. image()->blockSignals(false); image()->unlock(); } canvasBase()->initializeImage(); /** * Dirty hack alert */ d->zoomManager.zoomController()->setAspectMode(true); if (viewConverter()) { viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE); } connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*))); connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*))); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF))); KisNodeSP activeNode = document()->preActivatedNode(); - document()->setPreActivatedNode(0); // to make sure that we don't keep a reference to a layer the user can later delete. if (!activeNode) { activeNode = image()->rootLayer()->lastChild(); } while (activeNode && !activeNode->inherits("KisLayer")) { activeNode = activeNode->prevSibling(); } setCurrentNode(activeNode); zoomManager()->updateImageBoundsSnapping(); } void KisView::slotSavingFinished() { if (d->viewManager && d->viewManager->mainWindow()) { d->viewManager->mainWindow()->updateCaption(); } } KisPrintJob * KisView::createPrintJob() { return new KisPrintJob(image()); } void KisView::slotImageResolutionChanged() { resetImageSizeAndScroll(false); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); // update KoUnit value for the document if (resourceProvider()) { resourceProvider()->resourceManager()-> setResource(KoCanvasResourceManager::Unit, d->canvas.unit()); } } void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint) { resetImageSizeAndScroll(true, oldStillPoint, newStillPoint); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); } diff --git a/libs/ui/KisView.h b/libs/ui/KisView.h index eb2c6b9b7e..93f848d5d4 100644 --- a/libs/ui/KisView.h +++ b/libs/ui/KisView.h @@ -1,281 +1,283 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2007 Thomas Zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VIEW_H #define KIS_VIEW_H #include #include #include #include #include "kritaui_export.h" #include "widgets/kis_floating_message.h" class KisDocument; class KisMainWindow; class KisPrintJob; class KisCanvasController; class KisZoomManager; class KisCanvas2; class KisViewManager; class KisDocument; class KisCanvasResourceProvider; class KisCoordinatesConverter; class KisInputManager; class KoZoomController; class KoZoomController; struct KoPageLayout; class KoCanvasResourceManager; // KDE classes class QAction; class KActionCollection; // Qt classes class QDragEnterEvent; class QDropEvent; class QPrintDialog; class QCloseEvent; class QStatusBar; /** * This class is used to display a @ref KisDocument. * * Multiple views can be attached to one document at a time. */ class KRITAUI_EXPORT KisView : public QWidget { Q_OBJECT public: /** * Creates a new view for the document. */ KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent = 0); ~KisView() override; QAction *undoAction() const; QAction *redoAction() const; // Temporary while teasing apart view and mainwindow void setViewManager(KisViewManager *view); KisViewManager *viewManager() const; public: /** * Retrieves the document object of this view. */ KisDocument *document() const; /** * Reset the view to show the given document. */ void setDocument(KisDocument *document); /** * Tells this view that its document has got deleted (called internally) */ void setDocumentDeleted(); /** * In order to print the document represented by this view a new print job should * be constructed that is capable of doing the printing. * The default implementation returns 0, which silently cancels printing. */ KisPrintJob * createPrintJob(); /** * Create a QPrintDialog based on the @p printJob */ QPrintDialog *createPrintDialog(KisPrintJob *printJob, QWidget *parent); /** * @return the KisMainWindow in which this view is currently. */ KisMainWindow *mainWindow() const; /** * @return the statusbar of the KisMainWindow in which this view is currently. */ QStatusBar *statusBar() const; /** * This adds a widget to the statusbar for this view. * If you use this method instead of using statusBar() directly, * KisView will take care of removing the items when the view GUI is deactivated * and readding them when it is reactivated. * The parameters are the same as QStatusBar::addWidget(). */ void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false); /** * Remove a widget from the statusbar for this view. */ void removeStatusBarItem(QWidget * widget); /** * Return the zoomController for this view. */ KoZoomController *zoomController() const; /// create a list of actions that when activated will change the unit on the document. QList createChangeUnitActions(bool addPixelUnit = false); public: /** * The zoommanager handles everything action-related to zooming */ KisZoomManager *zoomManager() const; /** * The CanvasController decorates the canvas with scrollbars * and knows where to start painting on the canvas widget, i.e., * the document offset. */ KisCanvasController *canvasController() const; KisCanvasResourceProvider *resourceProvider() const; /** * Filters events and sends them to canvas actions. Shared * among all the views/canvases * * NOTE: May be null while initialization! */ KisInputManager* globalInputManager() const; /** * @return the canvas object */ KisCanvas2 *canvasBase() const; /// @return the image this view is displaying KisImageWSP image() const; KisCoordinatesConverter *viewConverter() const; void resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint = QPointF(), const QPointF &newImageStillPoint = QPointF()); void setCurrentNode(KisNodeSP node); KisNodeSP currentNode() const; KisLayerSP currentLayer() const; KisMaskSP currentMask() const; /** * @brief softProofing * @return whether or not we're softproofing in this view. */ bool softProofing(); /** * @brief gamutCheck * @return whether or not we're using gamut warnings in this view. */ bool gamutCheck(); /// Convenience method to get at the active selection (the /// selection of the current layer, or, if that does not exist, /// the global selection. KisSelectionSP selection(); void notifyCurrentStateChanged(bool isCurrent); void setShowFloatingMessage(bool show); void showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment); bool canvasIsMirrored() const; + void syncLastActiveNodeToDocument(); + public Q_SLOTS: /** * Display a message in the status bar (calls QStatusBar::message()) * @todo rename to something more generic */ - void slotActionStatusText(const QString &text); + void slotActionStatusText(const QString &text, int timeout); /** * End of the message in the status bar (calls QStatusBar::clear()) * @todo rename to something more generic */ void slotClearStatusText(); /** * @brief slotSoftProofing set whether or not we're softproofing in this view. * Will be setting the same in the canvas belonging to the view. */ void slotSoftProofing(bool softProofing); /** * @brief slotGamutCheck set whether or not we're gamutchecking in this view. * Will be setting the same in the vans belonging to the view. */ void slotGamutCheck(bool gamutCheck); bool queryClose(); private Q_SLOTS: void slotImageNodeAdded(KisNodeSP node); void slotContinueAddNode(KisNodeSP newActiveNode); void slotImageNodeRemoved(KisNodeSP node); void slotContinueRemoveNode(KisNodeSP newActiveNode); Q_SIGNALS: // From KisImage void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void titleModified(QString,bool); void sigContinueAddNode(KisNodeSP newActiveNode); void sigContinueRemoveNode(KisNodeSP newActiveNode); protected: // QWidget overrides void dragEnterEvent(QDragEnterEvent * event) override; void dropEvent(QDropEvent * event) override; void closeEvent(QCloseEvent *event) override; /** * Generate a name for this view. */ QString newObjectName(); public Q_SLOTS: void slotLoadingFinished(); void slotSavingFinished(); void slotImageResolutionChanged(); void slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); private: class Private; Private * const d; static bool s_firstView; }; #endif diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index dbaafb813b..5723756fca 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1317 +1,1362 @@ /* * This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 1999 Carsten Pfeiffer * 2002 Patrick Julien * 2003-2011 Boudewijn Rempt * 2004 Clarence Dang * 2011 José Luis Vergara * 2017 L. E. Segovia * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "input/kis_input_manager.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_canvas_controller.h" #include "canvas/kis_grid_manager.h" #include "dialogs/kis_dlg_blacklist_cleanup.h" #include "input/kis_input_profile_manager.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_canvas_controls_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_composite_progress_proxy.h" +#include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_control_frame.h" #include "kis_coordinates_converter.h" #include "KisDocument.h" #include "kis_favorite_resource_manager.h" #include "kis_filter_manager.h" #include "kis_group_layer.h" #include +#include #include "kis_image_manager.h" #include #include "kis_mainwindow_observer.h" #include "kis_mask_manager.h" #include "kis_mimedata.h" #include "kis_mirror_manager.h" #include "kis_node_commands_adapter.h" #include "kis_node.h" #include "kis_node_manager.h" #include "kis_painting_assistants_manager.h" #include #include "kis_paintop_box.h" #include #include "KisPart.h" #include "KisPrintJob.h" -#include "kis_progress_widget.h" +#include #include "kis_resource_server_provider.h" #include "kis_selection.h" #include "kis_selection_manager.h" #include "kis_shape_controller.h" #include "kis_shape_layer.h" #include #include "kis_statusbar.h" #include #include #include "kis_tooltip_manager.h" #include #include "KisView.h" #include "kis_zoom_manager.h" #include "widgets/kis_floating_message.h" #include "kis_signal_auto_connection.h" #include "kis_script_manager.h" #include "kis_icon_utils.h" #include "kis_guides_manager.h" #include "kis_derived_resources.h" #include "dialogs/kis_delayed_save_dialog.h" +#include class BlockingUserInputEventFilter : public QObject { bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if(dynamic_cast(event) || dynamic_cast(event) || dynamic_cast(event)) { return true; } else { return false; } } }; class KisViewManager::KisViewManagerPrivate { public: KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent) : filterManager(_q) , createTemplate(0) , saveIncremental(0) , saveIncrementalBackup(0) , openResourcesDirectory(0) , rotateCanvasRight(0) , rotateCanvasLeft(0) , resetCanvasRotation(0) , wrapAroundAction(0) , levelOfDetailAction(0) , showRulersAction(0) , rulersTrackMouseAction(0) , zoomTo100pct(0) , zoomIn(0) , zoomOut(0) , selectionManager(_q) , statusBar(_q) , controlFrame(_q, _q_parent) , nodeManager(_q) , imageManager(_q) , gridManager(_q) , canvasControlsManager(_q) , paintingAssistantsManager(_q) , actionManager(_q, _actionCollection) , mainWindow(0) , showFloatingMessage(true) , currentImageView(0) , canvasResourceProvider(_q) , canvasResourceManager() , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q) , actionCollection(_actionCollection) , mirrorManager(_q) , inputManager(_q) , scriptManager(_q) , actionAuthor(0) { canvasResourceManager.addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisFlowResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisSizeResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter)); canvasResourceManager.addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator)); } public: KisFilterManager filterManager; KisAction *createTemplate; KisAction *createCopy; KisAction *saveIncremental; KisAction *saveIncrementalBackup; KisAction *openResourcesDirectory; KisAction *rotateCanvasRight; KisAction *rotateCanvasLeft; KisAction *resetCanvasRotation; KisAction *wrapAroundAction; KisAction *levelOfDetailAction; KisAction *showRulersAction; KisAction *rulersTrackMouseAction; KisAction *zoomTo100pct; KisAction *zoomIn; KisAction *zoomOut; KisAction *softProof; KisAction *gamutCheck; KisSelectionManager selectionManager; KisGuidesManager guidesManager; KisStatusBar statusBar; + QPointer persistentImageProgressUpdater; + + QScopedPointer persistentUnthreadedProgressUpdaterRouter; + QPointer persistentUnthreadedProgressUpdater; KisControlFrame controlFrame; KisNodeManager nodeManager; KisImageManager imageManager; KisGridManager gridManager; KisCanvasControlsManager canvasControlsManager; KisPaintingAssistantsManager paintingAssistantsManager; BlockingUserInputEventFilter blockingEventFilter; KisActionManager actionManager; QMainWindow* mainWindow; QPointer savedFloatingMessage; bool showFloatingMessage; QPointer currentImageView; KisCanvasResourceProvider canvasResourceProvider; KoCanvasResourceManager canvasResourceManager; KisSignalCompressor guiUpdateCompressor; KActionCollection *actionCollection; KisMirrorManager mirrorManager; KisInputManager inputManager; KisSignalAutoConnectionsStore viewConnections; KisScriptManager scriptManager; KSelectAction *actionAuthor; // Select action for author profile. QByteArray canvasState; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QFlags windowFlags; #endif bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force); }; KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection) : d(new KisViewManagerPrivate(this, _actionCollection, parent)) { d->actionCollection = _actionCollection; d->mainWindow = dynamic_cast(parent); d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager); connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout())); createActions(); setupManagers(); // These initialization functions must wait until KisViewManager ctor is complete. d->statusBar.setup(); + d->persistentImageProgressUpdater = + d->statusBar.progressUpdater()->startSubtask(1, "", true); + // reset state to "completed" + d->persistentImageProgressUpdater->setRange(0,100); + d->persistentImageProgressUpdater->setValue(100); + + d->persistentUnthreadedProgressUpdater = + d->statusBar.progressUpdater()->startSubtask(1, "", true); + // reset state to "completed" + d->persistentUnthreadedProgressUpdater->setRange(0,100); + d->persistentUnthreadedProgressUpdater->setValue(100); + + d->persistentUnthreadedProgressUpdaterRouter.reset( + new KoProgressUpdater(d->persistentUnthreadedProgressUpdater, + KoProgressUpdater::Unthreaded)); + d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true); + d->controlFrame.setup(parent); //Check to draw scrollbars after "Canvas only mode" toggle is created. this->showHideScrollbars(); QScopedPointer dummy(new KoDummyCanvasController(actionCollection())); KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data()); QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility())); connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)), d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice))); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int))); connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), resourceProvider(), SLOT(slotNodeActivated(KisNodeSP))); connect(resourceProvider()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), d->controlFrame.paintopBox(), SLOT(slotCanvasResourceChanged(int,QVariant))); connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*))); connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions())); KisInputProfileManager::instance()->loadProfiles(); KisConfig cfg; d->showFloatingMessage = cfg.showCanvasMessages(); } KisViewManager::~KisViewManager() { KisConfig cfg; if (resourceProvider() && resourceProvider()->currentPreset()) { cfg.writeEntry("LastPreset", resourceProvider()->currentPreset()->name()); cfg.writeKoColor("LastForeGroundColor",resourceProvider()->fgColor()); cfg.writeKoColor("LastBackGroundColor",resourceProvider()->bgColor()); } cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength()); delete d; } KActionCollection *KisViewManager::actionCollection() const { return d->actionCollection; } void KisViewManager::slotViewAdded(KisView *view) { - d->inputManager.addTrackedCanvas(view->canvasBase()); + // WARNING: this slot is called even when a view from another main windows is added! + // Don't expect \p view be a child of this view manager! + Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.showAllStatusBarItems(); } } void KisViewManager::slotViewRemoved(KisView *view) { - d->inputManager.removeTrackedCanvas(view->canvasBase()); + // WARNING: this slot is called even when a view from another main windows is removed! + // Don't expect \p view be a child of this view manager! + Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.hideAllStatusBarItems(); } } void KisViewManager::setCurrentView(KisView *view) { bool first = true; if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(false); d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor)); first = false; KisDocument* doc = d->currentImageView->document(); if (doc) { + doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater); doc->disconnect(this); } d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar); d->viewConnections.clear(); } - d->softProof->setChecked(view->softProofing()); - d->gamutCheck->setChecked(view->gamutCheck()); - - QPointerimageView = qobject_cast(view); + QPointer imageView = qobject_cast(view); + d->currentImageView = imageView; if (imageView) { + d->softProof->setChecked(imageView->softProofing()); + d->gamutCheck->setChecked(imageView->gamutCheck()); + // Wait for the async image to have loaded KisDocument* doc = view->document(); // connect(canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), d->statusBar, SLOT(documentMousePositionChanged(QPointF))); - d->currentImageView = imageView; // Restore the last used brush preset, color and background color. if (first) { KisConfig cfg; KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); QString lastPreset = cfg.readEntry("LastPreset", QString("Basic_tip_default")); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (!preset) { preset = rserver->resources().first(); } if (preset) { paintOpBox()->restoreResource(preset.data()); } const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor foreground(Qt::black, cs); d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground)); KoColor background(Qt::white, cs); d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background)); } KisCanvasController *canvasController = dynamic_cast(d->currentImageView->canvasController()); d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode())); d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15())); d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15())); d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation())); d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool))); d->wrapAroundAction->setChecked(canvasController->wrapAroundMode()); d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool))); d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode()); d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*))); d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool))); d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool))); d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100())); d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn())); d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut())); d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) ); d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) ); + // set up progrress reporting + doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater); + d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation())); + imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked()); imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked()); showHideScrollbars(); } d->filterManager.setView(imageView); d->selectionManager.setView(imageView); d->guidesManager.setView(imageView); d->nodeManager.setView(imageView); d->imageManager.setView(imageView); d->canvasControlsManager.setView(imageView); d->actionManager.setView(imageView); d->gridManager.setView(imageView); d->statusBar.setView(imageView); d->paintingAssistantsManager.setView(imageView); d->mirrorManager.setView(imageView); if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(true); d->currentImageView->canvasController()->activate(); d->currentImageView->canvasController()->setFocus(); - } - d->actionManager.updateGUI(); + d->viewConnections.addUniqueConnection( + image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), + resourceProvider(), SLOT(slotImageSizeChanged())); - d->viewConnections.addUniqueConnection( - image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), - resourceProvider(), SLOT(slotImageSizeChanged())); + d->viewConnections.addUniqueConnection( + image(), SIGNAL(sigResolutionChanged(double,double)), + resourceProvider(), SLOT(slotOnScreenResolutionChanged())); - d->viewConnections.addUniqueConnection( - image(), SIGNAL(sigResolutionChanged(double,double)), - resourceProvider(), SLOT(slotOnScreenResolutionChanged())); + d->viewConnections.addUniqueConnection( + image(), SIGNAL(sigNodeChanged(KisNodeSP)), + this, SLOT(updateGUI())); - d->viewConnections.addUniqueConnection( - image(), SIGNAL(sigNodeChanged(KisNodeSP)), - this, SLOT(updateGUI())); + d->viewConnections.addUniqueConnection( + d->currentImageView->zoomManager()->zoomController(), + SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), + resourceProvider(), SLOT(slotOnScreenResolutionChanged())); - d->viewConnections.addUniqueConnection( - view->zoomManager()->zoomController(), - SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), - resourceProvider(), SLOT(slotOnScreenResolutionChanged())); + } + + d->actionManager.updateGUI(); resourceProvider()->slotImageSizeChanged(); resourceProvider()->slotOnScreenResolutionChanged(); - Q_EMIT viewChanged(); } KoZoomController *KisViewManager::zoomController() const { if (d->currentImageView) { return d->currentImageView->zoomController(); } return 0; } KisImageWSP KisViewManager::image() const { if (document()) { return document()->image(); } return 0; } KisCanvasResourceProvider * KisViewManager::resourceProvider() { return &d->canvasResourceProvider; } KisCanvas2 * KisViewManager::canvasBase() const { if (d && d->currentImageView) { return d->currentImageView->canvasBase(); } return 0; } QWidget* KisViewManager::canvas() const { if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) { return d->currentImageView->canvasBase()->canvasWidget(); } return 0; } KisStatusBar * KisViewManager::statusBar() const { return &d->statusBar; } void KisViewManager::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { d->statusBar.addStatusBarItem(widget, stretch, permanent); } void KisViewManager::removeStatusBarItem(QWidget *widget) { d->statusBar.removeStatusBarItem(widget); } KisPaintopBox* KisViewManager::paintOpBox() const { return d->controlFrame.paintopBox(); } -KoProgressUpdater* KisViewManager::createProgressUpdater(KoProgressUpdater::Mode mode) +QPointer KisViewManager::createUnthreadedUpdater(const QString &name) +{ + return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false); +} + +QPointer KisViewManager::createThreadedUpdater(const QString &name) { - return new KisProgressUpdater(d->statusBar.progress(), document()->progressProxy(), mode); + return d->statusBar.progressUpdater()->startSubtask(1, name, false); } KisSelectionManager * KisViewManager::selectionManager() { return &d->selectionManager; } KisNodeSP KisViewManager::activeNode() { return d->nodeManager.activeNode(); } KisLayerSP KisViewManager::activeLayer() { return d->nodeManager.activeLayer(); } KisPaintDeviceSP KisViewManager::activeDevice() { return d->nodeManager.activePaintDevice(); } KisZoomManager * KisViewManager::zoomManager() { if (d->currentImageView) { return d->currentImageView->zoomManager(); } return 0; } KisFilterManager * KisViewManager::filterManager() { return &d->filterManager; } KisImageManager * KisViewManager::imageManager() { return &d->imageManager; } KisInputManager* KisViewManager::inputManager() const { return &d->inputManager; } KisSelectionSP KisViewManager::selection() { if (d->currentImageView) { return d->currentImageView->selection(); } return 0; } bool KisViewManager::selectionEditable() { KisLayerSP layer = activeLayer(); if (layer) { KoProperties properties; QList masks = layer->childNodes(QStringList("KisSelectionMask"), properties); if (masks.size() == 1) { return masks[0]->isEditable(); } } // global selection is always editable return true; } KisUndoAdapter * KisViewManager::undoAdapter() { if (!document()) return 0; KisImageWSP image = document()->image(); Q_ASSERT(image); return image->undoAdapter(); } void KisViewManager::createActions() { KisConfig cfg; d->saveIncremental = actionManager()->createAction("save_incremental_version"); connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental())); d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup"); connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup())); connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved())); d->saveIncremental->setEnabled(false); d->saveIncrementalBackup->setEnabled(false); KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger"); connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger())); d->createTemplate = actionManager()->createAction("create_template"); connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate())); d->createCopy = actionManager()->createAction("create_copy"); connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy())); d->openResourcesDirectory = actionManager()->createAction("open_resources_directory"); connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory())); d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right"); d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left"); d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation"); d->wrapAroundAction = actionManager()->createAction("wrap_around_mode"); d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode"); d->softProof = actionManager()->createAction("softProof"); d->gamutCheck = actionManager()->createAction("gamutCheck"); KisAction *tAction = actionManager()->createAction("showStatusBar"); tAction->setChecked(cfg.showStatusBar()); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool))); tAction = actionManager()->createAction("view_show_canvas_only"); tAction->setChecked(false); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool))); //Workaround, by default has the same shortcut as mirrorCanvas KisAction *a = dynamic_cast(actionCollection()->action("format_italic")); if (a) { a->setDefaultShortcut(QKeySequence()); } a = actionManager()->createAction("edit_blacklist_cleanup"); connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup())); d->showRulersAction = actionManager()->createAction("view_ruler"); d->showRulersAction->setChecked(cfg.showRulers()); connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool))); d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse"); d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse()); connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool))); d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct"); d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, ""); d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, ""); d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this); connect(d->actionAuthor, SIGNAL(triggered(const QString &)), this, SLOT(changeAuthorProfile(const QString &))); actionCollection()->addAction("settings_active_author", d->actionAuthor); slotUpdateAuthorProfileActions(); } void KisViewManager::setupManagers() { // Create the managers for filters, selections, layers etc. // XXX: When the currentlayer changes, call updateGUI on all // managers d->filterManager.setup(actionCollection(), actionManager()); d->selectionManager.setup(actionManager()); d->guidesManager.setup(actionManager()); d->nodeManager.setup(actionCollection(), actionManager()); d->imageManager.setup(actionManager()); d->gridManager.setup(actionManager()); d->paintingAssistantsManager.setup(actionManager()); d->canvasControlsManager.setup(actionManager()); d->mirrorManager.setup(actionCollection()); d->scriptManager.setup(actionCollection(), actionManager()); } void KisViewManager::updateGUI() { d->guiUpdateCompressor.start(); } void KisViewManager::slotBlacklistCleanup() { KisDlgBlacklistCleanup dialog; dialog.exec(); } KisNodeManager * KisViewManager::nodeManager() const { return &d->nodeManager; } KisActionManager* KisViewManager::actionManager() const { return &d->actionManager; } KisGridManager * KisViewManager::gridManager() const { return &d->gridManager; } KisGuidesManager * KisViewManager::guidesManager() const { return &d->guidesManager; } KisDocument *KisViewManager::document() const { if (d->currentImageView && d->currentImageView->document()) { return d->currentImageView->document(); } return 0; } KisScriptManager *KisViewManager::scriptManager() const { return &d->scriptManager; } int KisViewManager::viewCount() const { KisMainWindow *mw = qobject_cast(d->mainWindow); if (mw) { return mw->viewCount(); } return 0; } bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force) { const int busyWaitDelay = 1000; KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow); dialog.blockIfImageIsBusy(); return dialog.result() == QDialog::Accepted; } bool KisViewManager::blockUntilOperationsFinished(KisImageSP image) { return d->blockUntilOperationsFinishedImpl(image, false); } void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image) { d->blockUntilOperationsFinishedImpl(image, true); } void KisViewManager::slotCreateTemplate() { if (!document()) return; KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow()); } void KisViewManager::slotCreateCopy() { - if (!document()) return; - KisDocument *doc = KisPart::instance()->createDocument(); + KisDocument *srcDoc = document(); + if (!srcDoc) return; + + if (!this->blockUntilOperationsFinished(srcDoc->image())) return; - QString name = document()->documentInfo()->aboutInfo("name"); + KisDocument *doc = 0; + { + KisImageBarrierLocker l(srcDoc->image()); + doc = srcDoc->clone(); + } + KIS_SAFE_ASSERT_RECOVER_RETURN(doc); + + QString name = srcDoc->documentInfo()->aboutInfo("name"); if (name.isEmpty()) { name = document()->url().toLocalFile(); } name = i18n("%1 (Copy)", name); doc->documentInfo()->setAboutInfo("title", name); - KisImageWSP image = document()->image(); - KisImageSP newImage = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->colorSpace(), name); - newImage->setRootLayer(dynamic_cast(image->rootLayer()->clone().data())); - doc->setCurrentImage(newImage); + KisPart::instance()->addDocument(doc); KisMainWindow *mw = qobject_cast(d->mainWindow); mw->addViewAndNotifyLoadingCompleted(doc); } QMainWindow* KisViewManager::qtMainWindow() const { if (d->mainWindow) return d->mainWindow; //Fallback for when we have not yet set the main window. QMainWindow* w = qobject_cast(qApp->activeWindow()); if(w) return w; return mainWindow(); } void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow) { d->mainWindow = newMainWindow; } void KisViewManager::slotDocumentSaved() { d->saveIncremental->setEnabled(true); d->saveIncrementalBackup->setEnabled(true); } void KisViewManager::slotSaveIncremental() { if (!document()) return; bool foundVersion; bool fileAlreadyExists; bool isBackup; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // Find current version filenames // v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well // Considering our incremental version and backup scheme, format is filename_001~001.ext QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); foundVersion = matches.at(0).isEmpty() ? false : true; // Ensure compatibility with Save Incremental Backup // If this regex is not kept separate, the entire algorithm needs modification; // It's simpler to just add this. QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regexAux.indexIn(fileName); // Perform the search QStringList matchesAux = regexAux.capturedTexts(); isBackup = matchesAux.at(0).isEmpty() ? false : true; // If the filename has a version, prepare it for incrementation if (foundVersion) { version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "_" } else { + // TODO: this will not work with files extensions like jp2 // ...else, simply add a version to it so the next loop works QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(fileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(version); extensionPlusVersion.prepend("_"); fileName.replace(regex2, extensionPlusVersion); } // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("_"); if (!letter.isNull()) newVersion.append(letter); if (isBackup) { newVersion.append("~"); } else { newVersion.append("."); } fileName.replace(regex, newVersion); fileAlreadyExists = QFile(fileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } document()->setFileBatchMode(true); - document()->saveAs(QUrl::fromUserInput(fileName)); + document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) { mainWindow()->updateCaption(); } } void KisViewManager::slotSaveIncrementalBackup() { if (!document()) return; bool workingOnBackup; bool fileAlreadyExists; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // First, discover if working on a backup file, or a normal file QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); workingOnBackup = matches.at(0).isEmpty() ? false : true; if (workingOnBackup) { // Try to save incremental version (of backup), use letter for alt versions version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "~" // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); QString backupFileName = document()->localFilePath(); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("~"); if (!letter.isNull()) newVersion.append(letter); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QFile::copy(fileName, backupFileName); - document()->saveAs(QUrl::fromUserInput(fileName)); + document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); if (mainWindow()) mainWindow()->updateCaption(); } else { // if NOT working on a backup... // Navigate directory searching for latest backup version, ignore letters const quint8 HARDCODED_DIGIT_COUNT = 3; QString baseNewVersion = "000"; QString backupFileName = document()->localFilePath(); QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(backupFileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(baseNewVersion); extensionPlusVersion.prepend("~"); backupFileName.replace(regex2, extensionPlusVersion); // Save version with 1 number higher than the highest version found ignoring letters do { newVersion = baseNewVersion; newVersion.prepend("~"); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { // Prepare the base for new version filename, increment by 1 int intVersion = baseNewVersion.toInt(0); ++intVersion; baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) { baseNewVersion.prepend("0"); } } } while (fileAlreadyExists); // Save both as backup and on current file for interapplication workflow document()->setFileBatchMode(true); QFile::copy(fileName, backupFileName); - document()->saveAs(QUrl::fromUserInput(fileName)); + document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) mainWindow()->updateCaption(); } } void KisViewManager::disableControls() { // prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel // this is for Bug 250944 // the solution blocks all wheel, mouse and key event, while dragging with the freehand tool // see KisToolFreehand::initPaint() and endPaint() d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->installEventFilter(&d->blockingEventFilter); } } void KisViewManager::enableControls() { d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->removeEventFilter(&d->blockingEventFilter); } } void KisViewManager::showStatusBar(bool toggled) { KisMainWindow *mw = mainWindow(); if(mw && mw->statusBar()) { mw->statusBar()->setVisible(toggled); KisConfig cfg; cfg.setShowStatusBar(toggled); } } void KisViewManager::switchCanvasOnly(bool toggled) { KisConfig cfg; KisMainWindow* main = mainWindow(); if(!main) { dbgUI << "Unable to switch to canvas-only mode, main window not found"; return; } if (toggled) { d->canvasState = qtMainWindow()->saveState(); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) d->windowFlags = main->windowState(); #endif } if (cfg.hideStatusbarFullscreen()) { if (main->statusBar()) { if (!toggled) { if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->statusBar()->property("wasvisible").toBool()) { main->statusBar()->setVisible(true); } } } else { main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible()); main->statusBar()->setVisible(false); } } } if (cfg.hideDockersFullscreen()) { KisAction* action = qobject_cast(main->actionCollection()->action("view_toggledockers")); if (action) { action->setCheckable(true); if (toggled) { if (action->isChecked()) { cfg.setShowDockers(action->isChecked()); action->setChecked(false); } else { cfg.setShowDockers(false); } } else { action->setChecked(cfg.showDockers()); } } } // QT in windows does not return to maximized upon 4th tab in a row // https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/ if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) { if(toggled) { main->setWindowState( main->windowState() | Qt::WindowFullScreen); } else { main->setWindowState( main->windowState() & ~Qt::WindowFullScreen); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // If window was maximized prior to fullscreen, restore that if (d->windowFlags & Qt::WindowMaximized) { main->setWindowState( main->windowState() | Qt::WindowMaximized); } #endif } } if (cfg.hideMenuFullscreen()) { if (!toggled) { if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->menuBar()->property("wasvisible").toBool()) { main->menuBar()->setVisible(true); } } } else { main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible()); main->menuBar()->setVisible(false); } } if (cfg.hideToolbarFullscreen()) { QList toolBars = main->findChildren(); Q_FOREACH (QToolBar* toolbar, toolBars) { if (!toggled) { if (toolbar->dynamicPropertyNames().contains("wasvisible")) { if (toolbar->property("wasvisible").toBool()) { toolbar->setVisible(true); } } } else { toolbar->setProperty("wasvisible", toolbar->isVisible()); toolbar->setVisible(false); } } } showHideScrollbars(); if (toggled) { // show a fading heads-up display about the shortcut to go back showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.", actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon()); } else { main->restoreState(d->canvasState); } } void KisViewManager::toggleTabletLogger() { d->inputManager.toggleTabletLogger(); } void KisViewManager::openResourcesDirectory() { QString dir = KoResourcePaths::locateLocal("data", ""); QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); } void KisViewManager::updateIcons() { if (mainWindow()) { QList dockers = mainWindow()->dockWidgets(); Q_FOREACH (QDockWidget* dock, dockers) { dbgKrita << "name " << dock->objectName(); KoDockWidgetTitleBar* titlebar = dynamic_cast(dock->titleBarWidget()); if (titlebar) { titlebar->updateIcons(); } QObjectList objects; objects.append(dock); while (!objects.isEmpty()) { QObject* object = objects.takeFirst(); objects.append(object->children()); KisIconUtils::updateIconCommon(object); } } } } void KisViewManager::initializeStatusBarVisibility() { KisConfig cfg; d->mainWindow->statusBar()->setVisible(cfg.showStatusBar()); } void KisViewManager::guiUpdateTimeout() { d->nodeManager.updateGUI(); d->selectionManager.updateGUI(); d->filterManager.updateGUI(); if (zoomManager()) { zoomManager()->updateGUI(); } d->gridManager.updateGUI(); d->actionManager.updateGUI(); } void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->currentImageView) return; d->currentImageView->showFloatingMessageImpl(message, icon, timeout, priority, alignment); emit floatingMessageRequested(message, icon.name()); } KisMainWindow *KisViewManager::mainWindow() const { return qobject_cast(d->mainWindow); } void KisViewManager::showHideScrollbars() { if (!d->currentImageView) return; if (!d->currentImageView->canvasController()) return; KisConfig cfg; bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked(); if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } void KisViewManager::slotSaveShowRulersState(bool value) { KisConfig cfg; cfg.setShowRulers(value); } void KisViewManager::slotSaveRulersTrackMouseState(bool value) { KisConfig cfg; cfg.setRulersTrackMouse(value); } void KisViewManager::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisViewManager::changeAuthorProfile(const QString &profileName) { KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author"); if (profileName.isEmpty()) { appAuthorGroup.writeEntry("active-profile", ""); } else if (profileName == i18nc("choice for author profile", "Anonymous")) { appAuthorGroup.writeEntry("active-profile", "anonymous"); } else { appAuthorGroup.writeEntry("active-profile", profileName); } appAuthorGroup.sync(); Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { doc->documentInfo()->updateParameters(); } } void KisViewManager::slotUpdateAuthorProfileActions() { Q_ASSERT(d->actionAuthor); if (!d->actionAuthor) { return; } d->actionAuthor->clear(); d->actionAuthor->addAction(i18n("Default Author Profile")); d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous")); KConfigGroup authorGroup(KoGlobal::calligraConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); Q_FOREACH (const QString &profile , profiles) { d->actionAuthor->addAction(profile); } KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); if (profileName == "anonymous") { d->actionAuthor->setCurrentItem(1); } else if (profiles.contains(profileName)) { d->actionAuthor->setCurrentAction(profileName); } else { d->actionAuthor->setCurrentItem(0); } } diff --git a/libs/ui/KisViewManager.h b/libs/ui/KisViewManager.h index 3ddc7a98c9..6f2ed929c2 100644 --- a/libs/ui/KisViewManager.h +++ b/libs/ui/KisViewManager.h @@ -1,271 +1,273 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_GUI_CLIENT_H #define KIS_GUI_CLIENT_H #include #include #include #include -#include #include #include #include #include "kis_floating_message.h" class QPoint; class KisView; class KisCanvas2; class KisCanvasResourceProvider; class KisDocument; class KisFilterManager; class KisGridManager; class KisGuidesManager; class KisImageManager; class KisNodeManager; class KisPaintingAssistantsManager; class KisPaintopBox; class KisSelectionManager; class KisStatusBar; class KisUndoAdapter; class KisZoomManager; class KisPaintopBox; class KisActionManager; class KisScriptManager; class KisInputManager; +class KoUpdater; +class KoProgressUpdater; /** * Krita view class * * Following the broad model-view-controller idea this class shows you one view on the document. * There can be multiple views of the same document each in with independent settings for viewMode and zoom etc. */ class KRITAUI_EXPORT KisViewManager : public QObject { Q_OBJECT public: /** * Construct a new view on the krita document. * @param document the document we show. * @param parent a parent widget we show ourselves in. */ KisViewManager(QWidget *parent, KActionCollection *actionCollection); ~KisViewManager() override; /** * Retrieves the entire action collection. */ virtual KActionCollection* actionCollection() const; public: // Krita specific interfaces void setCurrentView(KisView *view); /// Return the image this view is displaying KisImageWSP image() const; KoZoomController *zoomController() const; /// The resource provider contains all per-view settings, such as /// current color, current paint op etc. KisCanvasResourceProvider * resourceProvider(); /// Return the canvasbase class KisCanvas2 * canvasBase() const; /// Return the actual widget that is displaying the current image QWidget* canvas() const; /// Return the wrapper class around the statusbar KisStatusBar * statusBar() const; /** * This adds a widget to the statusbar for this view. * If you use this method instead of using statusBar() directly, * KisView will take care of removing the items when the view GUI is deactivated * and readding them when it is reactivated. * The parameters are the same as QStatusBar::addWidget(). */ void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false); /** * Remove a widget from the statusbar for this view. */ void removeStatusBarItem(QWidget * widget); KisPaintopBox* paintOpBox() const; /// create a new progress updater - KoProgressUpdater *createProgressUpdater(KoProgressUpdater::Mode mode = KoProgressUpdater::Threaded); + QPointer createUnthreadedUpdater(const QString &name); + QPointer createThreadedUpdater(const QString &name); /// The selection manager handles everything action related to /// selections. KisSelectionManager *selectionManager(); /// The node manager handles everything about nodes KisNodeManager *nodeManager() const; KisActionManager *actionManager() const; /** * Convenience method to get at the active node, which may be * a layer or a mask or a selection */ KisNodeSP activeNode(); /// Convenience method to get at the active layer KisLayerSP activeLayer(); /// Convenience method to get at the active paint device KisPaintDeviceSP activeDevice(); /// The filtermanager handles everything action-related to filters KisFilterManager *filterManager(); /// The image manager handles everything action-related to the /// current image KisImageManager *imageManager(); /// Filters events and sends them to canvas actions KisInputManager *inputManager() const; /// Convenience method to get at the active selection (the /// selection of the current layer, or, if that does not exist, /// the global selection. KisSelectionSP selection(); /// Checks if the current global or local selection is editable bool selectionEditable(); /// The undo adapter is used to add commands to the undo stack KisUndoAdapter *undoAdapter(); KisDocument *document() const; KisScriptManager *scriptManager() const; int viewCount() const; /** * @brief blockUntilOperationsFinished blocks the GUI of the application until execution * of actions on \p image is finished * @param image the image which we should wait for * @return true if the image has finished execution of the actions, false if * the user cancelled operation */ bool blockUntilOperationsFinished(KisImageSP image); /** * @brief blockUntilOperationsFinished blocks the GUI of the application until execution * of actions on \p image is finished. Does *not* provide a "Cancel" button. So the * user is forced to wait. * @param image the image which we should wait for */ void blockUntilOperationsFinishedForced(KisImageSP image); public: KisGridManager * gridManager() const; KisGuidesManager * guidesManager() const; /// disable and enable toolbar controls. used for disabling them during painting. void enableControls(); void disableControls(); /// shows a floating message in the top right corner of the canvas void showFloatingMessage(const QString &message, const QIcon& icon, int timeout = 4500, KisFloatingMessage::Priority priority = KisFloatingMessage::Medium, int alignment = Qt::AlignCenter | Qt::TextWordWrap); /// @return the KoMaindow this view is in, or 0 KisMainWindow *mainWindow() const; /// The QMainWindow associated with this view. This is most likely going to be shell(), but /// when running as Gemini or Sketch, this will be set to the applications' own QMainWindow. /// This can be checked by qobject_casting to KisMainWindow to check the difference. QMainWindow* qtMainWindow() const; /// The mainWindow function will return the shell() value, unless this function is called /// with a non-null value. To make it return shell() again, simply pass null to this function. void setQtMainWindow(QMainWindow* newMainWindow); public Q_SLOTS: void switchCanvasOnly(bool toggled); void setShowFloatingMessage(bool show); void showHideScrollbars(); /// Visit all managers to update gui elements, e.g. enable / disable actions. /// This is heavy-duty call, so it uses a compressor. void updateGUI(); /// Update the style of all the icons void updateIcons(); void slotViewAdded(KisView *view); void slotViewRemoved(KisView *view); Q_SIGNALS: void floatingMessageRequested(const QString &message, const QString &iconName); /** * @brief viewChanged * sent out when the view has changed. */ void viewChanged(); private Q_SLOTS: void slotBlacklistCleanup(); void slotCreateTemplate(); void slotCreateCopy(); void slotDocumentSaved(); void slotSaveIncremental(); void slotSaveIncrementalBackup(); void showStatusBar(bool toggled); void toggleTabletLogger(); void openResourcesDirectory(); void initializeStatusBarVisibility(); void guiUpdateTimeout(); void changeAuthorProfile(const QString &profileName); void slotUpdateAuthorProfileActions(); void slotSaveShowRulersState(bool value); void slotSaveRulersTrackMouseState(bool value); private: void createActions(); void setupManagers(); /// The zoommanager handles everything action-related to zooming KisZoomManager * zoomManager(); private: class KisViewManagerPrivate; KisViewManagerPrivate * const d; }; #endif diff --git a/libs/ui/canvas/kis_animation_player.cpp b/libs/ui/canvas/kis_animation_player.cpp index 425e38d6a4..fdd264b3cd 100644 --- a/libs/ui/canvas/kis_animation_player.cpp +++ b/libs/ui/canvas/kis_animation_player.cpp @@ -1,544 +1,544 @@ /* * 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_player.h" #include #include #include //#define PLAYER_DEBUG_FRAMERATE #include "kis_global.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_image.h" #include "kis_canvas2.h" #include "kis_animation_frame_cache.h" #include "kis_signal_auto_connection.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_signal_compressor.h" #include #include #include #include #include #include "KisSyncedAudioPlayback.h" #include "kis_signal_compressor_with_param.h" #include "KisViewManager.h" #include "kis_icon_utils.h" #include "KisPart.h" #include "dialogs/KisAnimationCacheUpdateProgressDialog.h" using namespace boost::accumulators; typedef accumulator_set > FpsAccumulator; struct KisAnimationPlayer::Private { public: Private(KisAnimationPlayer *_q) : q(_q), realFpsAccumulator(tag::rolling_window::window_size = 24), droppedFpsAccumulator(tag::rolling_window::window_size = 24), droppedFramesPortion(tag::rolling_window::window_size = 24), dropFramesMode(true), nextFrameExpectedTime(0), expectedInterval(0), expectedFrame(0), lastTimerInterval(0), lastPaintedFrame(0), playbackStatisticsCompressor(1000, KisSignalCompressor::FIRST_INACTIVE), stopAudioOnScrubbingCompressor(100, KisSignalCompressor::POSTPONE), audioOffsetTolerance(-1) {} KisAnimationPlayer *q; bool useFastFrameUpload; bool playing; QTimer *timer; int initialFrame; int firstFrame; int lastFrame; qreal playbackSpeed; KisCanvas2 *canvas; KisSignalAutoConnectionsStore cancelStrokeConnections; QElapsedTimer realFpsTimer; FpsAccumulator realFpsAccumulator; FpsAccumulator droppedFpsAccumulator; FpsAccumulator droppedFramesPortion; bool dropFramesMode; QElapsedTimer playbackTime; int nextFrameExpectedTime; int expectedInterval; int expectedFrame; int lastTimerInterval; int lastPaintedFrame; KisSignalCompressor playbackStatisticsCompressor; QScopedPointer syncedAudio; QScopedPointer > audioSyncScrubbingCompressor; KisSignalCompressor stopAudioOnScrubbingCompressor; int audioOffsetTolerance; void stopImpl(bool doUpdates); int incFrame(int frame, int inc) { frame += inc; if (frame > lastFrame) { frame = firstFrame + frame - lastFrame - 1; } return frame; } qint64 frameToMSec(int value, int fps) { return qreal(value) / fps * 1000.0; } int msecToFrame(qint64 value, int fps) { return qreal(value) * fps / 1000.0; } }; KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas) : QObject(canvas) , m_d(new Private(this)) { m_d->useFastFrameUpload = false; m_d->playing = false; m_d->canvas = canvas; m_d->playbackSpeed = 1.0; m_d->timer = new QTimer(this); connect(m_d->timer, SIGNAL(timeout()), this, SLOT(slotUpdate())); m_d->timer->setSingleShot(true); connect(KisConfigNotifier::instance(), SIGNAL(dropFramesModeChanged()), SLOT(slotUpdateDropFramesMode())); slotUpdateDropFramesMode(); connect(&m_d->playbackStatisticsCompressor, SIGNAL(timeout()), this, SIGNAL(sigPlaybackStatisticsUpdated())); using namespace std::placeholders; std::function callback( std::bind(&KisAnimationPlayer::slotSyncScrubbingAudio, this, _1)); const int defaultScrubbingUdpatesDelay = 40; /* 40 ms == 25 fps */ m_d->audioSyncScrubbingCompressor.reset( new KisSignalCompressorWithParam(defaultScrubbingUdpatesDelay, callback, KisSignalCompressor::FIRST_ACTIVE)); m_d->stopAudioOnScrubbingCompressor.setDelay(defaultScrubbingUdpatesDelay); connect(&m_d->stopAudioOnScrubbingCompressor, SIGNAL(timeout()), SLOT(slotTryStopScrubbingAudio())); connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), SLOT(slotUpdateAudioChunkLength())); slotUpdateAudioChunkLength(); connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioChannelChanged()), SLOT(slotAudioChannelChanged())); connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SLOT(slotAudioVolumeChanged())); slotAudioChannelChanged(); } KisAnimationPlayer::~KisAnimationPlayer() {} void KisAnimationPlayer::slotUpdateDropFramesMode() { KisConfig cfg; m_d->dropFramesMode = cfg.animationDropFrames(); } void KisAnimationPlayer::slotSyncScrubbingAudio(int msecTime) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio); if (!m_d->syncedAudio->isPlaying()) { m_d->syncedAudio->play(msecTime); } else { m_d->syncedAudio->syncWithVideo(msecTime); } if (!isPlaying()) { m_d->stopAudioOnScrubbingCompressor.start(); } } void KisAnimationPlayer::slotTryStopScrubbingAudio() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio); if (m_d->syncedAudio && !isPlaying()) { m_d->syncedAudio->stop(); } } void KisAnimationPlayer::slotAudioChannelChanged() { KisImageAnimationInterface *interface = m_d->canvas->image()->animationInterface(); QString fileName = interface->audioChannelFileName(); QFileInfo info(fileName); if (info.exists() && !interface->isAudioMuted()) { m_d->syncedAudio.reset(new KisSyncedAudioPlayback(info.absoluteFilePath())); m_d->syncedAudio->setVolume(interface->audioVolume()); m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance); connect(m_d->syncedAudio.data(), SIGNAL(error(const QString &, const QString &)), SLOT(slotOnAudioError(const QString &, const QString &))); } else { m_d->syncedAudio.reset(); } } void KisAnimationPlayer::slotAudioVolumeChanged() { KisImageAnimationInterface *interface = m_d->canvas->image()->animationInterface(); if (m_d->syncedAudio) { m_d->syncedAudio->setVolume(interface->audioVolume()); } } void KisAnimationPlayer::slotOnAudioError(const QString &fileName, const QString &message) { QString errorMessage(i18nc("floating on-canvas message", "Cannot open audio: \"%1\"\nError: %2", fileName, message)); - m_d->canvas->viewManager()->showFloatingMessage(errorMessage, KisIconUtils::loadIcon("dialog-warning")); + m_d->canvas->viewManager()->showFloatingMessage(errorMessage, KisIconUtils::loadIcon("warning")); } void KisAnimationPlayer::connectCancelSignals() { m_d->cancelStrokeConnections.addConnection( m_d->canvas->image().data(), SIGNAL(sigUndoDuringStrokeRequested()), this, SLOT(slotCancelPlayback())); m_d->cancelStrokeConnections.addConnection( m_d->canvas->image().data(), SIGNAL(sigStrokeCancellationRequested()), this, SLOT(slotCancelPlayback())); m_d->cancelStrokeConnections.addConnection( m_d->canvas->image().data(), SIGNAL(sigStrokeEndRequested()), this, SLOT(slotCancelPlaybackSafe())); m_d->cancelStrokeConnections.addConnection( m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), this, SLOT(slotUpdatePlaybackTimer())); m_d->cancelStrokeConnections.addConnection( m_d->canvas->image()->animationInterface(), SIGNAL(sigFullClipRangeChanged()), this, SLOT(slotUpdatePlaybackTimer())); m_d->cancelStrokeConnections.addConnection( m_d->canvas->image()->animationInterface(), SIGNAL(sigPlaybackRangeChanged()), this, SLOT(slotUpdatePlaybackTimer())); } void KisAnimationPlayer::disconnectCancelSignals() { m_d->cancelStrokeConnections.clear(); } void KisAnimationPlayer::slotUpdateAudioChunkLength() { const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); const int animationFramePeriod = qMax(1, 1000 / animation->framerate()); KisConfig cfg; int scrubbingAudioUdpatesDelay = cfg.scrubbingAudioUpdatesDelay(); if (scrubbingAudioUdpatesDelay < 0) { scrubbingAudioUdpatesDelay = qMax(1, animationFramePeriod); } m_d->audioSyncScrubbingCompressor->setDelay(scrubbingAudioUdpatesDelay); m_d->stopAudioOnScrubbingCompressor.setDelay(scrubbingAudioUdpatesDelay); m_d->audioOffsetTolerance = cfg.audioOffsetTolerance(); if (m_d->audioOffsetTolerance < 0) { m_d->audioOffsetTolerance = animationFramePeriod; } if (m_d->syncedAudio) { m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance); } } void KisAnimationPlayer::slotUpdatePlaybackTimer() { m_d->timer->stop(); const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); const KisTimeRange &range = animation->playbackRange(); if (!range.isValid()) return; const int fps = animation->framerate(); m_d->initialFrame = animation->currentUITime(); m_d->firstFrame = range.start(); m_d->lastFrame = range.end(); m_d->expectedFrame = qBound(m_d->firstFrame, m_d->expectedFrame, m_d->lastFrame); m_d->expectedInterval = qreal(1000) / fps / m_d->playbackSpeed; m_d->lastTimerInterval = m_d->expectedInterval; if (m_d->syncedAudio) { m_d->syncedAudio->setSpeed(m_d->playbackSpeed); } m_d->timer->start(m_d->expectedInterval); if (m_d->playbackTime.isValid()) { m_d->playbackTime.restart(); } else { m_d->playbackTime.start(); } m_d->nextFrameExpectedTime = m_d->playbackTime.elapsed() + m_d->expectedInterval; } void KisAnimationPlayer::play() { { const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); const KisTimeRange &range = animation->playbackRange(); if (!range.isValid()) return; // when openGL is disabled, there is no animation cache if (m_d->canvas->frameCache()) { KisAnimationCacheUpdateProgressDialog dlg(200, KisPart::instance()->currentMainwindow()); dlg.regenerateRange(m_d->canvas->frameCache(), range, m_d->canvas->viewManager()); } } m_d->playing = true; slotUpdatePlaybackTimer(); m_d->expectedFrame = m_d->firstFrame; m_d->lastPaintedFrame = m_d->firstFrame; connectCancelSignals(); if (m_d->syncedAudio) { KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface(); m_d->syncedAudio->play(m_d->frameToMSec(m_d->firstFrame, animationInterface->framerate())); } } void KisAnimationPlayer::Private::stopImpl(bool doUpdates) { if (syncedAudio) { syncedAudio->stop(); } q->disconnectCancelSignals(); timer->stop(); playing = false; if (doUpdates) { KisImageAnimationInterface *animation = canvas->image()->animationInterface(); if (animation->currentUITime() == initialFrame) { canvas->refetchDataFromImage(); } else { animation->switchCurrentTimeAsync(initialFrame); } } emit q->sigPlaybackStopped(); } void KisAnimationPlayer::stop() { m_d->stopImpl(true); } void KisAnimationPlayer::forcedStopOnExit() { m_d->stopImpl(false); } bool KisAnimationPlayer::isPlaying() { return m_d->playing; } int KisAnimationPlayer::currentTime() { return m_d->lastPaintedFrame; } void KisAnimationPlayer::displayFrame(int time) { uploadFrame(time); } void KisAnimationPlayer::slotUpdate() { uploadFrame(-1); } void KisAnimationPlayer::uploadFrame(int frame) { KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface(); if (frame < 0) { const int currentTime = m_d->playbackTime.elapsed(); const int framesDiff = currentTime - m_d->nextFrameExpectedTime; const qreal framesDiffNorm = qreal(framesDiff) / m_d->expectedInterval; // qDebug() << ppVar(framesDiff) // << ppVar(m_d->expectedFrame) // << ppVar(framesDiffNorm) // << ppVar(m_d->lastTimerInterval); if (m_d->dropFramesMode) { const int numDroppedFrames = qMax(0, qRound(framesDiffNorm)); frame = m_d->incFrame(m_d->expectedFrame, numDroppedFrames); } else { frame = m_d->expectedFrame; } m_d->nextFrameExpectedTime = currentTime + m_d->expectedInterval; m_d->lastTimerInterval = qMax(0.0, m_d->lastTimerInterval - 0.5 * framesDiff); m_d->expectedFrame = m_d->incFrame(frame, 1); m_d->timer->start(m_d->lastTimerInterval); m_d->playbackStatisticsCompressor.start(); } if (m_d->syncedAudio) { const int msecTime = m_d->frameToMSec(frame, animationInterface->framerate()); if (isPlaying()) { slotSyncScrubbingAudio(msecTime); } else { m_d->audioSyncScrubbingCompressor->start(msecTime); } } if (m_d->canvas->frameCache() && m_d->canvas->frameCache()->uploadFrame(frame)) { m_d->canvas->updateCanvas(); m_d->useFastFrameUpload = true; emit sigFrameChanged(); } else { m_d->useFastFrameUpload = false; m_d->canvas->image()->barrierLock(true); m_d->canvas->image()->unlock(); // no OpenGL cache or the frame just not cached yet animationInterface->switchCurrentTimeAsync(frame); emit sigFrameChanged(); } if (!m_d->realFpsTimer.isValid()) { m_d->realFpsTimer.start(); } else { const int elapsed = m_d->realFpsTimer.restart(); m_d->realFpsAccumulator(elapsed); int numFrames = frame - m_d->lastPaintedFrame; if (numFrames < 0) { numFrames += m_d->lastFrame - m_d->firstFrame + 1; } m_d->droppedFramesPortion(qreal(int(numFrames != 1))); if (numFrames > 0) { m_d->droppedFpsAccumulator(qreal(elapsed) / numFrames); } #ifdef PLAYER_DEBUG_FRAMERATE qDebug() << " RFPS:" << 1000.0 / rolling_mean(m_d->realFpsAccumulator) << "DFPS:" << 1000.0 / rolling_mean(m_d->droppedFpsAccumulator) << ppVar(numFrames); #endif /* PLAYER_DEBUG_FRAMERATE */ } m_d->lastPaintedFrame = frame; } qreal KisAnimationPlayer::effectiveFps() const { return 1000.0 / rolling_mean(m_d->droppedFpsAccumulator); } qreal KisAnimationPlayer::realFps() const { return 1000.0 / rolling_mean(m_d->realFpsAccumulator); } qreal KisAnimationPlayer::framesDroppedPortion() const { return rolling_mean(m_d->droppedFramesPortion); } void KisAnimationPlayer::slotCancelPlayback() { stop(); } void KisAnimationPlayer::slotCancelPlaybackSafe() { /** * If there is no openGL support, then we have no (!) cache at * all. Therefore we should regenerate frame on every time switch, * which, yeah, can be very slow. What is more important, when * regenerating a frame animation interface will emit a * sigStrokeEndRequested() signal and we should ignore it. That is * not an ideal solution, because the user will be able to paint * on random frames while playing, but it lets users with faulty * GPUs see at least some preview of their animation. */ if (m_d->useFastFrameUpload) { stop(); } } qreal KisAnimationPlayer::playbackSpeed() { return m_d->playbackSpeed; } void KisAnimationPlayer::slotUpdatePlaybackSpeed(double value) { m_d->playbackSpeed = value; if (m_d->playing) { slotUpdatePlaybackTimer(); } } diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp index 31f380631d..b5ad6f5c00 100644 --- a/libs/ui/canvas/kis_canvas_controller.cpp +++ b/libs/ui/canvas/kis_canvas_controller.cpp @@ -1,304 +1,306 @@ /* * 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_canvas_controller.h" #include #include #include #include "kis_canvas_decoration.h" #include "kis_paintop_transformation_connector.h" #include "kis_coordinates_converter.h" #include "kis_canvas2.h" #include "kis_image.h" #include "KisViewManager.h" #include "KisView.h" #include "krita_utils.h" #include "kis_signal_compressor_with_param.h" static const int gRulersUpdateDelay = 80 /* ms */; struct KisCanvasController::Private { Private(KisCanvasController *qq) : q(qq), paintOpTransformationConnector(0) { using namespace std::placeholders; std::function callback( std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1)); mousePositionCompressor.reset( new KisSignalCompressorWithParam( gRulersUpdateDelay, callback, KisSignalCompressor::FIRST_ACTIVE)); } QPointer view; KisCoordinatesConverter *coordinatesConverter; KisCanvasController *q; KisPaintopTransformationConnector *paintOpTransformationConnector; QScopedPointer > mousePositionCompressor; void emitPointerPositionChangedSignals(QPoint pointerPos); void updateDocumentSizeAfterTransform(); void showRotationValueOnCanvas(); void showMirrorStateOnCanvas(); }; void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos) { if (!coordinatesConverter) return; QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } void KisCanvasController::Private::updateDocumentSizeAfterTransform() { // round the size of the area to the nearest integer instead of getting aligned rect QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size(); q->updateDocumentSize(widgetSize, true); KisCanvas2 *kritaCanvas = dynamic_cast(q->canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->notifyZoomChanged(); } KisCanvasController::KisCanvasController(QPointerparent, KActionCollection * actionCollection) : KoCanvasControllerWidget(actionCollection, parent), m_d(new Private(this)) { m_d->view = parent; } KisCanvasController::~KisCanvasController() { delete m_d; } void KisCanvasController::setCanvas(KoCanvasBase *canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); Q_ASSERT(kritaCanvas); m_d->coordinatesConverter = const_cast(kritaCanvas->coordinatesConverter()); KoCanvasControllerWidget::setCanvas(canvas); m_d->paintOpTransformationConnector = new KisPaintopTransformationConnector(kritaCanvas, this); } void KisCanvasController::changeCanvasWidget(QWidget *widget) { KoCanvasControllerWidget::changeCanvasWidget(widget); } void KisCanvasController::activate() { KoCanvasControllerWidget::activate(); } QPointF KisCanvasController::currentCursorPosition() const { KoCanvasBase *canvas = m_d->view->canvasBase(); QWidget *canvasWidget = canvas->canvasWidget(); const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos()); return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget); } void KisCanvasController::keyPressEvent(QKeyEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::keyPressEvent() * to avoid activation of Pan and Default tool activation shortcuts */ Q_UNUSED(event); } void KisCanvasController::wheelEvent(QWheelEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::wheelEvent() * to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea */ Q_UNUSED(event); } bool KisCanvasController::eventFilter(QObject *watched, QEvent *event) { KoCanvasBase *canvas = this->canvas(); if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false; if (event->type() == QEvent::MouseMove) { QMouseEvent *mevent = static_cast(event); m_d->mousePositionCompressor->start(mevent->pos()); } else if (event->type() == QEvent::TabletMove) { QTabletEvent *tevent = static_cast(event); m_d->mousePositionCompressor->start(tevent->pos()); + } else if (event->type() == QEvent::FocusIn) { + m_d->view->syncLastActiveNodeToDocument(); } return false; } void KisCanvasController::updateDocumentSize(const QSize &sz, bool recalculateCenter) { KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter); emit documentSizeChanged(); } void KisCanvasController::Private::showMirrorStateOnCanvas() { bool isXMirrored = coordinatesConverter->xAxisMirrored(); view->viewManager()-> showFloatingMessage( i18nc("floating message about mirroring", "Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } void KisCanvasController::mirrorCanvas(bool enable) { QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showMirrorStateOnCanvas(); } void KisCanvasController::Private::showRotationValueOnCanvas() { qreal rotationAngle = coordinatesConverter->rotationAngle(); view->viewManager()-> showFloatingMessage( i18nc("floating message about rotation", "Rotation: %1° ", KritaUtils::prettyFormatReal(rotationAngle)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } void KisCanvasController::rotateCanvas(qreal angle) { QPoint newOffset = m_d->coordinatesConverter->rotate(m_d->coordinatesConverter->widgetCenterPoint(), angle); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::rotateCanvasRight15() { rotateCanvas(15.0); } void KisCanvasController::rotateCanvasLeft15() { rotateCanvas(-15.0); } qreal KisCanvasController::rotation() const { return m_d->coordinatesConverter->rotationAngle(); } void KisCanvasController::resetCanvasRotation() { QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint()); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::slotToggleWrapAroundMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); if (!canvas()->canvasIsOpenGL() && value) { m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n" "To visualize wrap-around mode, enable OpenGL."), QIcon()); } kritaCanvas->setWrapAroundViewingMode(value); kritaCanvas->image()->setWrapAroundModePermitted(value); } bool KisCanvasController::wrapAroundMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->wrapAroundViewingMode(); } void KisCanvasController::slotToggleLevelOfDetailMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->setLodAllowedInCanvas(value); bool result = levelOfDetailMode(); if (!value || result) { m_d->view->viewManager()->showFloatingMessage( i18n("Instant Preview Mode: %1", result ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } else { QString reason; if (!kritaCanvas->canvasIsOpenGL()) { reason = i18n("Instant Preview is only supported with OpenGL activated"); } else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode || kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) { QString filteringMode = kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ? i18n("Bilinear") : i18n("Nearest Neighbour"); reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode); } m_d->view->viewManager()->showFloatingMessage( i18n("Failed activating Instant Preview mode!\n\n%1", reason), QIcon(), 5000, KisFloatingMessage::Low); } } bool KisCanvasController::levelOfDetailMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->lodAllowedInCanvas(); } diff --git a/libs/ui/dialogs/kis_dlg_blacklist_cleanup.cpp b/libs/ui/dialogs/kis_dlg_blacklist_cleanup.cpp index 147a57ecee..adaa3403ac 100644 --- a/libs/ui/dialogs/kis_dlg_blacklist_cleanup.cpp +++ b/libs/ui/dialogs/kis_dlg_blacklist_cleanup.cpp @@ -1,69 +1,69 @@ /* * * Copyright (c) 2012 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_dlg_blacklist_cleanup.h" #include #include #include #include #include #include #include #include KisDlgBlacklistCleanup::KisDlgBlacklistCleanup() { setCaption(i18n("Cleanup resource files")); setButtons(Ok | Cancel); setDefaultButton(Ok); QWidget* page = new QWidget(this); setupUi(page); setMainWidget(page); - labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32)); + labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); } void KisDlgBlacklistCleanup::accept() { QDialog::accept(); if (cbRemovePresets->isChecked()) { KisResourceServerProvider::instance()->paintOpPresetServer()->removeBlackListedFiles(); } if (cbRemoveBrushes->isChecked()) { KisResourceServerProvider::instance()->brushBlacklistCleanup(); } if (cbRemoveWorkspaces->isChecked()) { KisResourceServerProvider::instance()->workspaceServer()->removeBlackListedFiles(); } if (cbRemoveColorsets->isChecked()) { KoResourceServerProvider::instance()->paletteServer()->removeBlackListedFiles(); } if (cbRemoveGradients->isChecked()) { KoResourceServerProvider::instance()->gradientServer()->removeBlackListedFiles(); } if (cbRemovePattern->isChecked()) { KoResourceServerProvider::instance()->patternServer()->removeBlackListedFiles(); } if (cbRemoveBundles->isChecked()) { KisResourceServerProvider::instance()->resourceBundleServer()->removeBlackListedFiles(); } } diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 0740842d84..18cf5e8099 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1126 +1,1140 @@ /* * 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()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground()); + KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); + cursorColor.fromQColor(cfg.getCursorMainColor()); + cursorColorBtutton->setColor(cursorColor); + 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)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground(true)); + KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); + cursorColor.fromQColor(cfg.getCursorMainColor(true)); + cursorColorBtutton->setColor(cursorColor); } 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 & colorSpaceId) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; 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(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))); + + KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); + gridColor.fromQColor(cfg.getPixelGridColor()); + pixelGridColorButton->setColor(gridColor); + pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100); + grpPixelGrid->setEnabled(true); + grpPixelGrid->setChecked(cfg.pixelGridEnabled()); } 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)); + KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); + gridColor.fromQColor(cfg.getPixelGridColor(true)); + pixelGridColorButton->setColor(gridColor); + pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); + grpPixelGrid->setEnabled(true); + grpPixelGrid->setChecked(cfg.pixelGridEnabled(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.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked()); 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()); + cfg.setCursorMainColor(dialog->m_general->cursorColorBtutton->color().toQColor()); + cfg.setPixelGridColor(dialog->m_displaySettings->pixelGridColorButton->color().toQColor()); + cfg.setPixelGridDrawingThreshold(dialog->m_displaySettings->pixelGridDrawingThresholdBox->value() / 100); + cfg.enablePixelGrid(dialog->m_displaySettings->grpPixelGrid->isChecked()); + dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 4333422880..9531fe4ba1 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,643 +1,655 @@ /* * 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); + // copy the projection to avoid extra round of updates! + initShapeLayer(controller, _rhs.m_d->paintDevice); Q_FOREACH (KoShape *shape, _rhs.shapes()) { - addShape(shape->cloneShape()); + KoShape *clonedShape = shape->cloneShape(); + KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } + addShape(clonedShape); } } 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()); + KoShape *clonedShape = shape->cloneShape(); + KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } + addShape(clonedShape); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { - addShape(shape->cloneShape()); + KoShape *clonedShape = shape->cloneShape(); + KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } + addShape(clonedShape); } } 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) +void KisShapeLayer::initShapeLayer(KoShapeBasedDocumentBase* controller, KisPaintDeviceSP copyFromProjection) { 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); + + if (!copyFromProjection) { + m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); + m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); + m_d->paintDevice->setParentNode(this); + } else { + m_d->paintDevice = new KisPaintDevice(*copyFromProjection); + } 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); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; 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/flake/kis_shape_layer.h b/libs/ui/flake/kis_shape_layer.h index 3118d208c0..5cb1e7aca7 100644 --- a/libs/ui/flake/kis_shape_layer.h +++ b/libs/ui/flake/kis_shape_layer.h @@ -1,181 +1,181 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * * 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_SHAPE_LAYER_H_ #define KIS_SHAPE_LAYER_H_ #include #include #include #include #include class QRect; class QIcon; class QRect; class QString; class KoShapeManager; class KoStore; class KoViewConverter; class KoShapeBasedDocumentBase; class KoDocumentResourceManager; const QString KIS_SHAPE_LAYER_ID = "KisShapeLayer"; /** A KisShapeLayer contains any number of non-krita flakes, such as path shapes, text shapes and anything else people come up with. The KisShapeLayer has a shapemanager and a canvas of its own. The canvas paints onto the projection, and the projection is what we render in Krita. This means that no matter how many views you have, you cannot have a different view on your shapes per view. XXX: what about removing shapes? */ class KRITAUI_EXPORT KisShapeLayer : public KisExternalLayer, public KoShapeLayer, public KisDelayedUpdateNodeInterface { Q_OBJECT public: KisShapeLayer(KoShapeBasedDocumentBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity); KisShapeLayer(const KisShapeLayer& _rhs); KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller); /** * Merge constructor. * * Creates a new layer as a merge of two existing layers. * * This is used by createMergedLayer() */ KisShapeLayer(const KisShapeLayer& _merge, const KisShapeLayer &_addShapes); ~KisShapeLayer() override; private: - void initShapeLayer(KoShapeBasedDocumentBase* controller); + void initShapeLayer(KoShapeBasedDocumentBase* controller, KisPaintDeviceSP copyFromProjection = 0); public: KisNodeSP clone() const override { return new KisShapeLayer(*this); } bool allowAsChild(KisNodeSP) const override; void setImage(KisImageWSP image) override; KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer) override; void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) override; public: // KoShape overrides bool isSelectable() const { return false; } void setParent(KoShapeContainer *parent); // KisExternalLayer implementation QIcon icon() const override; void resetCache() override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; qint32 x() const override; qint32 y() const override; void setX(qint32) override; void setY(qint32) override; bool accept(KisNodeVisitor&) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KoShapeManager *shapeManager() const; static bool saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt); static QList createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize); bool saveLayer(KoStore * store) const; bool loadLayer(KoStore* store); KUndo2Command* crop(const QRect & rect) override; KUndo2Command* transform(const QTransform &transform) override; bool visible(bool recursive = false) const override; void setVisible(bool visible, bool isLoading = false) override; /** * Forces a repaint of a shape layer without waiting for an event loop * calling a delayed timer update. If you want to see the result of the shape * layer right here and right now, you should do: * * shapeLayer->setDirty(); * shapeLayer->image()->waitForDone(); * shapeLayer->forceUpdateTimedNode(); * shapeLayer->image()->waitForDone(); * */ void forceUpdateTimedNode() override; protected: using KoShape::isVisible; bool loadSvg(QIODevice *device, const QString &baseXmlDir); friend class ShapeLayerContainerModel; KoViewConverter* converter() const; Q_SIGNALS: /** * These signals are forwarded from the local shape manager * This is done because we switch KoShapeManager and therefore * KoSelection in KisCanvas2, so we need to connect local managers * to the UI as well. * * \see comment in the constructor of KisCanvas2 */ void selectionChanged(); void currentLayerChanged(const KoShapeLayer *layer); Q_SIGNALS: /** * A signal + slot to synchronize UI and image * threads. Image thread emits the signal, UI * thread performes the action */ void sigMoveShapes(const QPointF &diff); private Q_SLOTS: void slotMoveShapes(const QPointF &diff); private: QList shapesToBeTransformed(); private: struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/flake/kis_shape_selection.cpp b/libs/ui/flake/kis_shape_selection.cpp index 63e26909e7..ce7c337328 100644 --- a/libs/ui/flake/kis_shape_selection.cpp +++ b/libs/ui/flake/kis_shape_selection.cpp @@ -1,384 +1,386 @@ /* * Copyright (c) 2010 Sven Langkamp * 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_selection.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_shape_selection_model.h" #include "kis_shape_selection_canvas.h" #include "kis_take_all_shapes_command.h" #include "kis_image_view_converter.h" #include "kis_shape_layer.h" #include KisShapeSelection::KisShapeSelection(KisImageWSP image, KisSelectionWSP selection) : KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this)) , m_image(image) { Q_ASSERT(m_image); setShapeId("KisShapeSelection"); setSelectable(false); m_converter = new KisImageViewConverter(image); m_canvas = new KisShapeSelectionCanvas(); m_canvas->shapeManager()->addShape(this); m_model->setObjectName("KisShapeSelectionModel"); m_model->moveToThread(image->thread()); m_canvas->setObjectName("KisShapeSelectionCanvas"); m_canvas->moveToThread(image->thread()); } KisShapeSelection::~KisShapeSelection() { m_model->setShapeSelection(0); delete m_canvas; delete m_converter; } KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection) : KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this)) { m_image = rhs.m_image; m_converter = new KisImageViewConverter(m_image); m_canvas = new KisShapeSelectionCanvas(); m_canvas->shapeManager()->addShape(this); Q_FOREACH (KoShape *shape, rhs.shapes()) { - this->addShape(shape->cloneShape()); + KoShape *clonedShape = shape->cloneShape(); + KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } + this->addShape(clonedShape); } } KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection) { return new KisShapeSelection(*this, selection); } bool KisShapeSelection::saveSelection(KoStore * store) const { const QSizeF sizeInPx = m_image->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_image->xRes(), sizeInPx.height() / m_image->yRes()); return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeSelection::loadSelection(KoStore* store) { QSizeF fragmentSize; // unused! // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(m_image->xRes(), m_image->yRes())); const qreal resolutionPPI = 72.0 * m_image->xRes(); QList shapes; if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); shapes = KisShapeLayer::createShapesFromSvg(&storeDev, "", m_image->bounds(), resolutionPPI, m_canvas->shapeController()->resourceManager(), &fragmentSize); store->close(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { dbgKrita << 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()) { dbgKrita << "No office:body found!"; //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { dbgKrita << "No office:drawing found!"; //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { dbgKrita << "No office:drawing found!"; //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)); } else { dbgKrita << "No master page found!"; return false; } KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); KoShapeLoadingContext shapeContext(context, 0); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { 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 KisShapeSelection::setUpdatesEnabled(bool enabled) { m_model->setUpdatesEnabled(enabled); } bool KisShapeSelection::updatesEnabled() const { return m_model->updatesEnabled(); } KUndo2Command* KisShapeSelection::resetToEmpty() { return new KisTakeAllShapesCommand(this, true); } bool KisShapeSelection::isEmpty() const { return !m_model->count(); } QPainterPath KisShapeSelection::outlineCache() const { return m_outline; } bool KisShapeSelection::outlineCacheValid() const { return true; } void KisShapeSelection::recalculateOutlineCache() { QList shapesList = shapes(); QPainterPath outline; Q_FOREACH (KoShape * shape, shapesList) { QTransform shapeMatrix = shape->absoluteTransformation(0); outline = outline.united(shapeMatrix.map(shape->outline())); } QTransform resolutionMatrix; resolutionMatrix.scale(m_image->xRes(), m_image->yRes()); m_outline = resolutionMatrix.map(outline); } void KisShapeSelection::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection) { Q_ASSERT(projection); Q_ASSERT(m_image); QRectF boundingRect = outlineCache().boundingRect(); renderSelection(projection, boundingRect.toAlignedRect()); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r) { Q_ASSERT(projection); renderSelection(projection, r); } void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& r) { Q_ASSERT(projection); Q_ASSERT(m_image); const qint32 MASK_IMAGE_WIDTH = 256; const qint32 MASK_IMAGE_HEIGHT = 256; QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32); QPainter maskPainter(&polygonMaskImage); maskPainter.setRenderHint(QPainter::Antialiasing, true); // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) { for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) { maskPainter.fillRect(polygonMaskImage.rect(), Qt::black); maskPainter.translate(-x, -y); maskPainter.fillPath(outlineCache(), Qt::white); maskPainter.translate(x, y); qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH); qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT); KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight)); do { (*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y)); } while (it.nextPixel()); } } } KoShapeManager* KisShapeSelection::shapeManager() const { return m_canvas->shapeManager(); } KisShapeSelectionFactory::KisShapeSelectionFactory() : KoShapeFactoryBase("KisShapeSelection", "selection shape container") { setHidden(true); } void KisShapeSelection::moveX(qint32 x) { Q_FOREACH (KoShape* shape, shapeManager()->shapes()) { if (shape != this) { QPointF pos = shape->position(); shape->setPosition(QPointF(pos.x() + x/m_image->xRes(), pos.y())); } } } void KisShapeSelection::moveY(qint32 y) { Q_FOREACH (KoShape* shape, shapeManager()->shapes()) { if (shape != this) { QPointF pos = shape->position(); shape->setPosition(QPointF(pos.x(), pos.y() + y/m_image->yRes())); } } } // TODO same code as in vector layer, refactor! KUndo2Command* KisShapeSelection::transform(const QTransform &transform) { QList shapes = m_canvas->shapeManager()->shapes(); if(shapes.isEmpty()) return 0; QTransform realTransform = m_converter->documentToView() * transform * m_converter->viewToDocument(); QList oldTransformations; QList newTransformations; // this code won't work if there are shapes, that inherit the transformation from the parent container. // the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that. Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); if (dynamic_cast(shape)) { newTransformations.append(oldTransform); } else { QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform*oldTransform); } } return new KoShapeTransformCommand(shapes, oldTransformations, newTransformations); } diff --git a/libs/ui/forms/wdgdisplaysettings.ui b/libs/ui/forms/wdgdisplaysettings.ui index deb5017970..40ed87889c 100644 --- a/libs/ui/forms/wdgdisplaysettings.ui +++ b/libs/ui/forms/wdgdisplaysettings.ui @@ -1,362 +1,431 @@ WdgDisplaySettings 0 0 - 504 + 619 685 0 0 Display Miscellaneous Color channels in color false Enable curve anti-aliasing Enable selection outline anti-aliasing Hide layer thumbnail popup Transparency Checkerboard Pattern S&ize: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter intCheckSize px 256 32 Qt::Horizontal 40 20 Colors: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Horizontal 40 20 If checked, the checkers will move when scrolling the canvas. Determines whether the checks will stay put or whether they will scroll together with the canvas &Move checkers when scrolling true - + Qt::Vertical 20 40 0 0 Open&GL true 0 0 0 Nearest Neighbour Bilinear Filtering Trilinear Filtering High Quality Filtering <html><head/><body><p>Try to disable vsync for Krita. This makes painting more responsive. Uncheck only when experiencing crashes with some GPU/driver combinations.</p></body></html> Disable vsync (needs restart) true 0 0 <html><head/><body><p>Use Texture Buffering. This can be faster on some GPU/Driver combinations (like Intel) or broken on some others (like AMD/Radeon).</p></body></html> Use texture buffer 0 0 Scaling Mode: Canvas border Color: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Horizontal 40 20 Hide Scrollbars false Selection Overlay Color: 0 0 + + + + Pixel Grid + + + true + + + + + + Color: + + + + + + + % + + + 6400.000000000000000 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Start showing at: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorButton QPushButton
kis_color_button.h
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui index 7edacee5df..677fb048c7 100644 --- a/libs/ui/forms/wdggeneralsettings.ui +++ b/libs/ui/forms/wdggeneralsettings.ui @@ -1,684 +1,704 @@ 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 + + + + Cursor Color: + + + + + + + + 48 + 25 + + + + + + + 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 Enable Touch Painting 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 a31e331e3a..b0b0dc8e5a 100644 --- a/libs/ui/forms/wdgpaintopsettings.ui +++ b/libs/ui/forms/wdgpaintopsettings.ui @@ -1,568 +1,714 @@ WdgPaintOpSettings 0 0 - 1405 + 1431 512 Brush Editor + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 5 + + + + + + false + 0 + + 3 + 1 + + 3 + + + 3 + 0 4 120 0 - QFrame::StyledPanel + QFrame::NoFrame - QFrame::Raised + QFrame::Plain + + + 0 - 20 + 5 5 - 5 + 0 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 + 0 5 - 5 + 0 0 0 Load Engine Defaults - - - - - 0 - 0 - - - - Reload Preset - - - Qt::Horizontal 40 20 - - - - 0 - 0 - - - - - 0 - 0 - - + - Name: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Overwrite Brush - - - - 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 + Save New Brush Preset... - true + false - 6 + 5 - 0 + 5 - 0 + 5 - 0 + 5 - 0 + 5 - QFrame::StyledPanel + QFrame::NoFrame - QFrame::Raised + QFrame::Plain + + + 0 + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + 0 0 250 300 - - Fill preset area with current icon - Fill area with gradient Fill area with background color Reset area to white + + 10 + + + 10 + - + - + 0 0 - 35 - 20 + 30 + 30 - - + + + 30 + 30 + + + + + false + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + QPainter::Antialiasing - + + + + 200 + 0 + + + + Current Brush Name + + + 0 + + + 5 + + + + + - + 0 0 - - - 35 - 30 - + + - - Settings + + + + + + + 0 + 0 + + + + - + - + 0 0 - + - 35 - 30 + 50 + 16777215 - - Scratchpad + + - + true - + - 0 + 250 0 - - Current Brush Name + + + + + + + 0 + 0 + - - 0 + + Save - - 20 + + + + + + Cancel Qt::Horizontal 40 20 + + + + Show: + + + + + + + + 0 + 0 + + + + + 35 + 20 + + + + Presets + + + + + + + + 0 + 0 + + + + + 35 + 30 + + + + Settings + + + + + + + + 0 + 0 + + + + + 35 + 30 + + + + Scratchpad + + + true + + + + + + + + 5 + + + 5 + + + 5 + + + 10 + + + 5 + 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/forms/wdgsavebrushpreset.ui b/libs/ui/forms/wdgsavebrushpreset.ui new file mode 100644 index 0000000000..fe91d5e344 --- /dev/null +++ b/libs/ui/forms/wdgsavebrushpreset.ui @@ -0,0 +1,226 @@ + + + WdgSaveBrushPreset + + + + 0 + 0 + 450 + 312 + + + + + 0 + 0 + + + + Save Brush Preset + + + + 13 + + + + + + + Brush Name: + + + + + + + + 220 + 0 + + + + + + + + + 14 + + + + BrushName + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 18 + + + + + 2 + + + + + Paint in this area + + + true + + + + + + + + 0 + 0 + + + + + 200 + 200 + + + + + 200 + 200 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 6 + + + + + Load Existing Thumbnail + + + + + + + Load Scratchpad Thumbnail + + + + + + + Load Image + + + + + + + Clear Thumbnail + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Save + + + + + + + + + + KisScratchPad + QWidget +
kis_scratch_pad.h
+ 1 +
+
+ + +
diff --git a/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp index c5552870ae..93a51cd2c7 100644 --- a/libs/ui/input/kis_input_manager_p.cpp +++ b/libs/ui/input/kis_input_manager_p.cpp @@ -1,531 +1,536 @@ /* * 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_input_manager_p.h" #include #include #include #include #include "kis_input_manager.h" #include "kis_config.h" #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_input_profile_manager.h" /** * This hungry class EventEater encapsulates event masking logic. * * Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after * tablet events. Those events are sent in order to allow widgets that haven't * implemented tablet specific functionality to seamlessly behave as if one were * using a mouse. These synthetic events are *supposed* to be optional, or at * least come with a flag saying "This is a fake event!!" but neither of those * methods is trustworthy. (This is correct as of Qt 5.4 + Xorg.) * * Qt 5.4 provides no reliable way to see if a user's tablet is being hovered * over the pad, since it converts all tablethover events into mousemove, with * no option to turn this off. Moreover, sometimes the MouseButtonPress event * from the tapping their tablet happens BEFORE the TabletPress event. This * means we have to resort to a somewhat complicated logic. What makes this * truly a joke is that we are not guaranteed to observe TabletProximityEnter * events when we're using a tablet, either, you may only see an Enter event. * * Once we see tablet events heading our way, we can say pretty confidently that * every mouse event is fake. There are two painful cases to consider - a * mousePress event could arrive before the tabletPress event, or it could * arrive much later, e.g. after tabletRelease. The first was only seen on Linux * with Qt's XInput2 code, the solution was to hold onto mousePress events * temporarily and wait for tabletPress later, this is contained in git history * but is now removed. The second case is currently handled by the * eatOneMousePress function, which waits as long as necessary to detect and * block a single mouse press event. */ static bool isMouseEventType(QEvent::Type t) { return (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease || t == QEvent::MouseButtonDblClick); } bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event ) { Q_UNUSED(target) auto debugEvent = [&](int i) { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED %1:]").arg(i); QMouseEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); } }; if (peckish && event->type() == QEvent::MouseButtonPress // Drop one mouse press following tabletPress or touchBegin && (static_cast(event)->button() == Qt::LeftButton)) { peckish = false; debugEvent(1); return true; } else if (isMouseEventType(event->type()) && (hungry // On Mac, we need mouse events when the tablet is in proximity, but not pressed down // since tablet move events are not generated until after tablet press. #ifndef Q_OS_MAC || (eatSyntheticEvents && static_cast(event)->source() != Qt::MouseEventNotSynthesized) #endif )) { // Drop mouse events if enabled or event was synthetic & synthetic events are disabled debugEvent(2); return true; } return false; // All clear - let this one through! } void KisInputManager::Private::EventEater::activate() { if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Start blocking mouse events"; } hungry = true; } void KisInputManager::Private::EventEater::deactivate() { if (hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Stop blocking mouse events"; } hungry = false; } void KisInputManager::Private::EventEater::eatOneMousePress() { // Enable on other platforms if getting full-pressure splotches peckish = true; } bool KisInputManager::Private::ignoringQtCursorEvents() { return eventEater.hungry; } void KisInputManager::Private::setMaskSyntheticEvents(bool value) { eventEater.eatSyntheticEvents = value; } void KisInputManager::Private::setTabletActive(bool value) { tabletActive = value; } KisInputManager::Private::Private(KisInputManager *qq) : q(qq) , moveEventCompressor(10 /* ms */, KisSignalCompressor::FIRST_ACTIVE) , priorityEventFilterSeqNo(0) , canvasSwitcher(this, qq) { KisConfig cfg; moveEventCompressor.setDelay(cfg.tabletEventsDelay()); testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents(); testingCompressBrushEvents = cfg.testingCompressBrushEvents(); } static const int InputWidgetsThreshold = 2000; static const int OtherWidgetsThreshold = 400; KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p) : QObject(p), d(_d), eatOneMouseStroke(false), focusSwitchThreshold(InputWidgetsThreshold) { } void KisInputManager::Private::CanvasSwitcher::setupFocusThreshold(QObject* object) { QWidget *widget = qobject_cast(object); KIS_SAFE_ASSERT_RECOVER_RETURN(widget); thresholdConnections.clear(); thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus())); } void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas) { QObject *canvasWidget = canvas->canvasWidget(); if (!canvasResolver.contains(canvasWidget)) { canvasResolver.insert(canvasWidget, canvas); d->q->setupAsEventFilter(canvasWidget); canvasWidget->installEventFilter(this); setupFocusThreshold(canvasWidget); focusSwitchThreshold.setEnabled(false); d->canvas = canvas; d->toolProxy = dynamic_cast(canvas->toolProxy()); } else { KIS_ASSERT_RECOVER_RETURN(d->canvas == canvas); } } void KisInputManager::Private::CanvasSwitcher::removeCanvas(KisCanvas2 *canvas) { QObject *widget = canvas->canvasWidget(); canvasResolver.remove(widget); if (d->eventsReceiver == widget) { d->q->setupAsEventFilter(0); } widget->removeEventFilter(this); } bool isInputWidget(QWidget *w) { if (!w) return false; QList types; types << QLatin1String("QAbstractSlider"); types << QLatin1String("QAbstractSpinBox"); types << QLatin1String("QLineEdit"); types << QLatin1String("QTextEdit"); types << QLatin1String("QPlainTextEdit"); types << QLatin1String("QComboBox"); types << QLatin1String("QKeySequenceEdit"); Q_FOREACH (const QLatin1String &type, types) { if (w->inherits(type.data())) { return true; } } return false; } bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event ) { if (canvasResolver.contains(object)) { switch (event->type()) { case QEvent::FocusIn: { QFocusEvent *fevent = static_cast(event); KisCanvas2 *canvas = canvasResolver.value(object); + + // only relevant canvases from the same main window should be + // registered in the switcher + KIS_SAFE_ASSERT_RECOVER_BREAK(canvas); + if (canvas != d->canvas) { eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason); } d->canvas = canvas; d->toolProxy = dynamic_cast(canvas->toolProxy()); d->q->setupAsEventFilter(object); object->removeEventFilter(this); object->installEventFilter(this); setupFocusThreshold(object); focusSwitchThreshold.setEnabled(false); QEvent event(QEvent::Enter); d->q->eventFilter(object, &event); break; } case QEvent::FocusOut: { focusSwitchThreshold.setEnabled(true); break; } case QEvent::Enter: { break; } case QEvent::Leave: { focusSwitchThreshold.stop(); break; } case QEvent::Wheel: { QWidget *widget = static_cast(object); widget->setFocus(); break; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::TabletPress: case QEvent::TabletRelease: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { eatOneMouseStroke--; return true; } break; case QEvent::MouseButtonDblClick: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { return true; } break; case QEvent::MouseMove: case QEvent::TabletMove: { QWidget *widget = static_cast(object); if (!widget->hasFocus()) { const int delay = isInputWidget(QApplication::focusWidget()) ? InputWidgetsThreshold : OtherWidgetsThreshold; focusSwitchThreshold.setDelayThreshold(delay); focusSwitchThreshold.start(); } } break; default: break; } } return QObject::eventFilter(object, event); } KisInputManager::Private::ProximityNotifier::ProximityNotifier(KisInputManager::Private *_d, QObject *p) : QObject(p), d(_d) {} bool KisInputManager::Private::ProximityNotifier::eventFilter(QObject* object, QEvent* event ) { switch (event->type()) { case QEvent::TabletEnterProximity: d->debugEvent(event); // Tablet proximity events are unreliable AND fake mouse events do not // necessarily come after tablet events, so this is insufficient. // d->eventEater.eatOneMousePress(); // Qt sends fake mouse events instead of hover events, so not very useful. // Don't block mouse events on tablet since tablet move events are not generated until // after tablet press. #ifndef Q_OS_OSX d->blockMouseEvents(); #else // Notify input manager that tablet proximity is entered for Genius tablets. d->setTabletActive(true); #endif break; case QEvent::TabletLeaveProximity: d->debugEvent(event); d->allowMouseEvents(); #ifdef Q_OS_OSX d->setTabletActive(false); #endif break; default: break; } return QObject::eventFilter(object, event); } void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, Qt::MouseButtons buttons) { KisStrokeShortcut *strokeShortcut = new KisStrokeShortcut(action, index); QList buttonList; if(buttons & Qt::LeftButton) { buttonList << Qt::LeftButton; } if(buttons & Qt::RightButton) { buttonList << Qt::RightButton; } if(buttons & Qt::MidButton) { buttonList << Qt::MidButton; } if(buttons & Qt::XButton1) { buttonList << Qt::XButton1; } if(buttons & Qt::XButton2) { buttonList << Qt::XButton2; } if (buttonList.size() > 0) { strokeShortcut->setButtons(QSet::fromList(modifiers), QSet::fromList(buttonList)); matcher.addShortcut(strokeShortcut); } else { delete strokeShortcut; } } void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int index, const QList &keys) { if (keys.size() == 0) return; KisSingleActionShortcut *keyShortcut = new KisSingleActionShortcut(action, index); //Note: Ordering is important here, Shift + V is different from V + Shift, //which is the reason we use the last key here since most users will enter //shortcuts as "Shift + V". Ideally this should not happen, but this is //the way the shortcut matcher is currently implemented. QList allKeys = keys; Qt::Key key = allKeys.takeLast(); QSet modifiers = QSet::fromList(allKeys); keyShortcut->setKey(modifiers, key); matcher.addShortcut(keyShortcut); } void KisInputManager::Private::addWheelShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction) { KisSingleActionShortcut *keyShortcut = new KisSingleActionShortcut(action, index); KisSingleActionShortcut::WheelAction a; switch(wheelAction) { case KisShortcutConfiguration::WheelUp: a = KisSingleActionShortcut::WheelUp; break; case KisShortcutConfiguration::WheelDown: a = KisSingleActionShortcut::WheelDown; break; case KisShortcutConfiguration::WheelLeft: a = KisSingleActionShortcut::WheelLeft; break; case KisShortcutConfiguration::WheelRight: a = KisSingleActionShortcut::WheelRight; break; default: return; } keyShortcut->setWheel(QSet::fromList(modifiers), a); matcher.addShortcut(keyShortcut); } void KisInputManager::Private::addTouchShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { KisTouchShortcut *shortcut = new KisTouchShortcut(action, index); switch(gesture) { case KisShortcutConfiguration::PinchGesture: shortcut->setMinimumTouchPoints(2); shortcut->setMaximumTouchPoints(2); break; case KisShortcutConfiguration::PanGesture: shortcut->setMinimumTouchPoints(3); shortcut->setMaximumTouchPoints(10); break; default: break; } matcher.addShortcut(shortcut); } void KisInputManager::Private::setupActions() { QList actions = KisInputProfileManager::instance()->actions(); Q_FOREACH (KisAbstractInputAction *action, actions) { KisToolInvocationAction *toolAction = dynamic_cast(action); if(toolAction) { defaultInputAction = toolAction; } } connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged())); if(KisInputProfileManager::instance()->currentProfile()) { q->profileChanged(); } } bool KisInputManager::Private::processUnhandledEvent(QEvent *event) { bool retval = false; if (forwardAllEventsToTool || event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { defaultInputAction->processUnhandledEvent(event); retval = true; } return retval && !forwardAllEventsToTool; } bool KisInputManager::Private::tryHidePopupPalette() { if (canvas->isPopupPaletteVisible()) { canvas->slotShowPopupPalette(); return true; } return false; } #ifdef HAVE_X11 inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y()); } inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y()); } #endif void KisInputManager::Private::blockMouseEvents() { eventEater.activate(); } void KisInputManager::Private::allowMouseEvents() { eventEater.deactivate(); } void KisInputManager::Private::eatOneMousePress() { eventEater.eatOneMousePress(); } void KisInputManager::Private::resetCompressor() { compressedMoveEvent.reset(); moveEventCompressor.stop(); } bool KisInputManager::Private::handleCompressedTabletEvent(QEvent *event) { bool retval = false; if (!matcher.pointerMoved(event)) { toolProxy->forwardHoverEvent(event); } retval = true; event->setAccepted(true); return retval; } diff --git a/libs/ui/input/wintab/kis_tablet_support_win.cpp b/libs/ui/input/wintab/kis_tablet_support_win.cpp index 4a9a247b9c..1ecd9f4983 100644 --- a/libs/ui/input/wintab/kis_tablet_support_win.cpp +++ b/libs/ui/input/wintab/kis_tablet_support_win.cpp @@ -1,979 +1,979 @@ /* * Copyright (c) 2013 Digia Plc and/or its subsidiary(-ies). * Copyright (c) 2013 Boudewijn Rempt * Copyright (c) 2013 Dmitry Kazakov * Copyright (c) 2015 Michael Abrahams * Copyright (c) 2015 The Qt Company Ltd. * * 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_tablet_support_win_p.h" #include #include "kis_tablet_support_win.h" // #include "kis_tablet_support.h" #include #include #include #include #include #include #include #include #include #include #include #define Q_PI M_PI #include #include // For "inline tool switches" #include #include #include "kis_screen_size_choice_dialog.h" // NOTE: we stub out qwindowcontext.cpp::347 to disable Qt's own tablet support. // Note: The definition of the PACKET structure in pktdef.h depends on this define. #define PACKETDATA (PK_X | PK_Y | PK_BUTTONS | PK_TIME | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION | PK_CURSOR | PK_Z) #include "pktdef.h" QT_BEGIN_NAMESPACE enum { PacketMode = 0, TabletPacketQSize = 128, DeviceIdMask = 0xFF6, // device type mask && device color mask CursorTypeBitMask = 0x0F06 // bitmask to find the specific cursor type (see Wacom FAQ) }; /* * * Krita extensions begin here * * */ QWindowsTabletSupport *QTAB = 0; static QPointer targetWindow = 0; //< Window receiving last tablet event static QPointer qt_tablet_target = 0; //< Widget receiving last tablet event static bool dialogOpen = false; //< KisTabletSupportWin is not a Q_OBJECT and can't accept dialog signals HWND createDummyWindow(const QString &className, const wchar_t *windowName, WNDPROC wndProc) { if (!wndProc) wndProc = DefWindowProc; WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = wndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = (HINSTANCE)GetModuleHandle(0); wc.hCursor = 0; wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW); wc.hIcon = 0; wc.hIconSm = 0; wc.lpszMenuName = 0; wc.lpszClassName = (wchar_t*)className.utf16(); ATOM atom = RegisterClassEx(&wc); if (!atom) qErrnoWarning("Registering tablet fake window class failed."); return CreateWindowEx(0, (wchar_t*)className.utf16(), windowName, WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, NULL, (HINSTANCE)GetModuleHandle(0), NULL); } void printContext(const LOGCONTEXT &lc) { dbgTablet << "# Getting current context data:"; dbgTablet << ppVar(lc.lcName); dbgTablet << ppVar(lc.lcDevice); dbgTablet << ppVar(lc.lcInOrgX); dbgTablet << ppVar(lc.lcInOrgY); dbgTablet << ppVar(lc.lcInExtX); dbgTablet << ppVar(lc.lcInExtY); dbgTablet << ppVar(lc.lcOutOrgX); dbgTablet << ppVar(lc.lcOutOrgY); dbgTablet << ppVar(lc.lcOutExtX); dbgTablet << ppVar(lc.lcOutExtY); dbgTablet << ppVar(lc.lcSysOrgX); dbgTablet << ppVar(lc.lcSysOrgY); dbgTablet << ppVar(lc.lcSysExtX); dbgTablet << ppVar(lc.lcSysExtY); dbgTablet << "Qt Desktop Geometry" << QApplication::desktop()->geometry(); } static QRect mapToNative(const QRect &qRect, int m_factor) { return QRect(qRect.x() * m_factor, qRect.y() * m_factor, qRect.width() * m_factor, qRect.height() * m_factor); } static inline QEvent::Type mouseEventType(QEvent::Type t) { return (t == QEvent::TabletMove ? QEvent::MouseMove : t == QEvent::TabletPress ? QEvent::MouseButtonPress : t == QEvent::TabletRelease ? QEvent::MouseButtonRelease : QEvent::None); } static inline bool isMouseEventType(QEvent::Type t) { return (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease); } static QPoint mousePosition() { POINT p; GetCursorPos(&p); return QPoint(p.x, p.y); } QWindowsWinTab32DLL QWindowsTabletSupport::m_winTab32DLL; void KisTabletSupportWin::init() { if (!QWindowsTabletSupport::m_winTab32DLL.init()) { qWarning() << "Failed to initialize Wintab"; return; } QTAB = QWindowsTabletSupport::create(); // Refresh tablet context after tablet rotated, screen added, etc. QObject::connect(qApp->primaryScreen(), &QScreen::geometryChanged, [=](const QRect & geometry){ delete QTAB; QTAB = QWindowsTabletSupport::create(); }); } // Derived from qwidgetwindow. // // The work done by processTabletEvent from qguiapplicationprivate is divided // between here and translateTabletPacketEvent. static void handleTabletEvent(QWidget *windowWidget, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButton button, Qt::MouseButtons buttons, qreal pressure,int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uniqueId, Qt::KeyboardModifiers modifiers, QEvent::Type type, LONG time) { // Lock in target window if (type == QEvent::TabletPress) { targetWindow = windowWidget; dbgInput << "Locking target window" << targetWindow; } else if ((type == QEvent::TabletRelease || buttons == Qt::NoButton) && (targetWindow != 0)) { dbgInput << "Releasing target window" << targetWindow; targetWindow = 0; } if (!windowWidget) // Should never happen return; // We do this instead of constructing the event e beforehand const QPoint localPos = local.toPoint(); const QPoint globalPos = global.toPoint(); if (type == QEvent::TabletPress) { QWidget *widget = windowWidget->childAt(localPos); if (!widget) widget = windowWidget; qt_tablet_target = widget; } QWidget *finalDestination = qt_tablet_target; if (!finalDestination) { finalDestination = windowWidget->childAt(localPos); } if ((type == QEvent::TabletRelease || buttons == Qt::NoButton) && (qt_tablet_target != 0)) { dbgInput << "releasing tablet target" << qt_tablet_target; qt_tablet_target = 0; } if (finalDestination) { // The event was specified relative to windowWidget, so we remap it QPointF delta = global - globalPos; QPointF mapped = finalDestination->mapFromGlobal(global.toPoint()) + delta; QTabletEvent ev(type, mapped, global, device, pointerType, pressure, xTilt, yTilt, tangentialPressure, rotation, z, modifiers, uniqueId, button, buttons); ev.setTimestamp(time); QGuiApplication::sendEvent(finalDestination, &ev); if (ev.isAccepted()) { // dbgTablet << "Tablet event" << type << "accepted" << "by target widget" << finalDestination; } else { // Turn off eventEater send a synthetic mouse event. // dbgTablet << "Tablet event" << type << "rejected; sending mouse event to" << finalDestination; qt_tablet_target = 0; // We shouldn't ever get a widget accepting a tablet event from this // call, so we won't worry about any interactions with our own // widget-locking code. // QWindow *target = platformScreen->topLevelAt(globalPos); // if (!target) return; // QPointF windowLocal = global - QPointF(target->mapFromGlobal(QPoint())) + delta; // QWindowSystemInterface::handleTabletEvent(target, ev.timestamp(), windowLocal, // global, device, pointerType, // buttons, pressure, xTilt, yTilt, // tangentialPressure, rotation, z, // uniqueId, modifiers); } } else { qt_tablet_target = 0; targetWindow = 0; } } /** * This is a default implementation of a class for converting the * WinTab value of the buttons pressed to the Qt buttons. This class * may be substituted from the UI. */ struct DefaultButtonsConverter { void convert(DWORD btnOld, DWORD btnNew, Qt::MouseButton *button, Qt::MouseButtons *buttons, const QWindowsTabletDeviceData &tdd) { int pressedButtonValue = btnNew ^ btnOld; *button = buttonValueToEnum(pressedButtonValue, tdd); *buttons = Qt::NoButton; for (int i = 0; i < 3; i++) { int btn = 0x1 << i; if (btn & btnNew) { Qt::MouseButton convertedButton = buttonValueToEnum(btn, tdd); *buttons |= convertedButton; /** * If a button that is present in hardware input is * mapped to a Qt::NoButton, it means that it is going * to be eaten by the driver, for example by its * "Pan/Scroll" feature. Therefore we shouldn't handle * any of the events associated to it. So just return * Qt::NoButton here. */ if (convertedButton == Qt::NoButton) { /** * Sometimes the driver-handled sortcuts are just * keyboard modifiers, so ideally we should handle * them as well. The problem is that we cannot * know if the shortcut was a pan/zoom action or a * shortcut. So here we use a "hackish" approash. * We just check if any modifier has been pressed * and, if so, pass the button to Krita. Of * course, if the driver uses some really complex * shortcuts like "Shift + stylus btn" to generate * some recorded shortcut, it will not work. But I * guess it will be ok for th emost of the * usecases. * * WARNING: this hack will *not* work if you bind * any non-modifier key to the stylus * button, e.g. Space. */ const Qt::KeyboardModifiers keyboardModifiers = QApplication::queryKeyboardModifiers(); if (KisTabletDebugger::instance()->shouldEatDriverShortcuts() || keyboardModifiers == Qt::NoModifier) { *button = Qt::NoButton; *buttons = Qt::NoButton; break; } } } } } private: Qt::MouseButton buttonValueToEnum(DWORD button, const QWindowsTabletDeviceData &tdd) { const int leftButtonValue = 0x1; const int middleButtonValue = 0x2; const int rightButtonValue = 0x4; const int doubleClickButtonValue = 0x7; button = tdd.buttonsMap.value(button); return button == leftButtonValue ? Qt::LeftButton : button == rightButtonValue ? Qt::RightButton : button == doubleClickButtonValue ? Qt::MiddleButton : button == middleButtonValue ? Qt::MiddleButton : button ? Qt::LeftButton /* fallback item */ : Qt::NoButton; } }; static DefaultButtonsConverter *globalButtonsConverter = new DefaultButtonsConverter(); /* * * Krita extensions end here * * */ // This is the WndProc for a single additional hidden window used to collect tablet events. extern "C" LRESULT QT_WIN_CALLBACK qWindowsTabletSupportWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WT_PROXIMITY: if (QTAB->translateTabletProximityEvent(wParam, lParam)) return 0; break; case WT_PACKET: if (QTAB->translateTabletPacketEvent()) return 0; break; } return DefWindowProc(hwnd, message, wParam, lParam); } // Scale tablet coordinates to screen coordinates. static inline int sign(int x) { return x >= 0 ? 1 : -1; } inline QPointF QWindowsTabletDeviceData::scaleCoordinates(int coordX, int coordY, const QRect &targetArea) const { const int targetX = targetArea.x(); const int targetY = targetArea.y(); const int targetWidth = targetArea.width(); const int targetHeight = targetArea.height(); const qreal x = sign(targetWidth) == sign(maxX) ? ((coordX - minX) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX : ((qAbs(maxX) - (coordX - minX)) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX; const qreal y = sign(targetHeight) == sign(maxY) ? ((coordY - minY) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY : ((qAbs(maxY) - (coordY - minY)) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY; return QPointF(x, y); } /*! \class QWindowsWinTab32DLL QWindowsTabletSupport \brief Functions from wintabl32.dll shipped with WACOM tablets used by QWindowsTabletSupport. \internal \ingroup qt-lighthouse-win */ bool QWindowsWinTab32DLL::init() { if (wTInfo) return true; QLibrary library(QStringLiteral("wintab32")); if (!library.load()) { - qWarning() << "Could not load wintab32 dll"; + qWarning() << QString("Could not load wintab32 dll: %1").arg(library.errorString()); return false; } wTOpen = (PtrWTOpen) library.resolve("WTOpenW"); wTClose = (PtrWTClose) library.resolve("WTClose"); wTInfo = (PtrWTInfo) library.resolve("WTInfoW"); wTEnable = (PtrWTEnable) library.resolve("WTEnable"); wTOverlap = (PtrWTOverlap) library.resolve("WTOverlap"); wTPacketsGet = (PtrWTPacketsGet) library.resolve("WTPacketsGet"); wTGet = (PtrWTGet) library.resolve("WTGetW"); wTQueueSizeGet = (PtrWTQueueSizeGet) library.resolve("WTQueueSizeGet"); wTQueueSizeSet = (PtrWTQueueSizeSet) library.resolve("WTQueueSizeSet"); if (wTOpen && wTClose && wTInfo && wTEnable && wTOverlap && wTPacketsGet && wTQueueSizeGet && wTQueueSizeSet) { return true; } qWarning() << "Could not resolve the following symbols:\n" << "\t wTOpen" << wTOpen << "\n" << "\t wtClose" << wTClose << "\n" << "\t wtInfo" << wTInfo << "\n" << "\t wTEnable" << wTEnable << "\n" << "\t wTOverlap" << wTOverlap << "\n" << "\t wTPacketsGet" << wTPacketsGet << "\n" << "\t wTQueueSizeGet" << wTQueueSizeGet << "\n" << "\t wTQueueSizeSet" << wTQueueSizeSet << "\n"; return false; } /*! \class QWindowsTabletSupport \brief Tablet support for Windows. Support for WACOM tablets. \sa http://www.wacomeng.com/windows/docs/Wintab_v140.htm \internal \since 5.2 \ingroup qt-lighthouse-win */ QWindowsTabletSupport::QWindowsTabletSupport(HWND window, HCTX context) : m_window(window) , m_context(context) , m_absoluteRange(20) , m_tiltSupport(false) , m_currentDevice(-1) { AXIS orientation[3]; // Some tablets don't support tilt, check if it is possible, if (QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_ORIENTATION, &orientation)) m_tiltSupport = orientation[0].axResolution && orientation[1].axResolution; } QWindowsTabletSupport::~QWindowsTabletSupport() { QWindowsTabletSupport::m_winTab32DLL.wTClose(m_context); DestroyWindow(m_window); } QWindowsTabletSupport *QWindowsTabletSupport::create() { const HWND window = createDummyWindow(QStringLiteral("TabletDummyWindow"), L"TabletDummyWindow", qWindowsTabletSupportWndProc); LOGCONTEXT lcMine; // build our context from the default context QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFSYSCTX, 0, &lcMine); // Go for the raw coordinates, the tablet event will return good stuff. The // defaults for lcOut rectangle are the desktop dimensions in pixels, which // means Wintab will do lossy rounding. Instead we specify this trivial // scaling for the output context, then do the scaling ourselves later to // obtain higher resolution coordinates. lcMine.lcOptions |= CXO_MESSAGES | CXO_CSRMESSAGES; lcMine.lcPktData = lcMine.lcMoveMask = PACKETDATA; lcMine.lcPktMode = PacketMode; lcMine.lcOutOrgX = 0; lcMine.lcOutExtX = lcMine.lcInExtX; lcMine.lcOutOrgY = 0; lcMine.lcOutExtY = -lcMine.lcInExtY; const HCTX context = QWindowsTabletSupport::m_winTab32DLL.wTOpen(window, &lcMine, true); if (!context) { dbgTablet << __FUNCTION__ << "Unable to open tablet."; DestroyWindow(window); return 0; } // Set the size of the Packet Queue to the correct size const int currentQueueSize = QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeGet(context); if (currentQueueSize != TabletPacketQSize) { if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, TabletPacketQSize)) { if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, currentQueueSize)) { qWarning() << "Unable to set queue size on tablet. The tablet will not work."; QWindowsTabletSupport::m_winTab32DLL.wTClose(context); DestroyWindow(window); return 0; } // cannot restore old size } // cannot set } // mismatch dbgTablet << "Opened tablet context " << context << " on window " << window << "changed packet queue size " << currentQueueSize << "->" << TabletPacketQSize; return new QWindowsTabletSupport(window, context); } unsigned QWindowsTabletSupport::options() const { UINT result = 0; m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_CTXOPTIONS, &result); return result; } QString QWindowsTabletSupport::description() const { const unsigned size = m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, 0); if (!size) return QString(); QScopedPointer winTabId(new TCHAR[size + 1]); m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, winTabId.data()); WORD implementationVersion = 0; m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_IMPLVERSION, &implementationVersion); WORD specificationVersion = 0; m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_SPECVERSION, &specificationVersion); const unsigned opts = options(); QString result = QString::fromLatin1("%1 specification: v%2.%3 implementation: v%4.%5 options: 0x%6") .arg(QString::fromWCharArray(winTabId.data())) .arg(specificationVersion >> 8).arg(specificationVersion & 0xFF) .arg(implementationVersion >> 8).arg(implementationVersion & 0xFF) .arg(opts, 0, 16); if (opts & CXO_MESSAGES) result += QStringLiteral(" CXO_MESSAGES"); if (opts & CXO_CSRMESSAGES) result += QStringLiteral(" CXO_CSRMESSAGES"); if (m_tiltSupport) result += QStringLiteral(" tilt"); return result; } void QWindowsTabletSupport::notifyActivate() { // Cooperate with other tablet applications, but when we get focus, I want to use the tablet. const bool result = QWindowsTabletSupport::m_winTab32DLL.wTEnable(m_context, true) && QWindowsTabletSupport::m_winTab32DLL.wTOverlap(m_context, true); dbgTablet << __FUNCTION__ << result; } static inline int indexOfDevice(const QVector &devices, qint64 uniqueId) { for (int i = 0; i < devices.size(); ++i) if (devices.at(i).uniqueId == uniqueId) return i; return -1; } static inline QTabletEvent::TabletDevice deviceType(const UINT cursorType) { if (((cursorType & 0x0006) == 0x0002) && ((cursorType & CursorTypeBitMask) != 0x0902)) return QTabletEvent::Stylus; if (cursorType == 0x4020) // Surface Pro 2 tablet device return QTabletEvent::Stylus; switch (cursorType & CursorTypeBitMask) { case 0x0802: return QTabletEvent::Stylus; case 0x0902: return QTabletEvent::Airbrush; case 0x0004: return QTabletEvent::FourDMouse; case 0x0006: return QTabletEvent::Puck; case 0x0804: return QTabletEvent::RotationStylus; default: break; } return QTabletEvent::NoDevice; }; static inline QTabletEvent::PointerType pointerType(unsigned pkCursor) { switch (pkCursor % 3) { // %3 for dual track case 0: return QTabletEvent::Cursor; case 1: return QTabletEvent::Pen; case 2: return QTabletEvent::Eraser; default: break; } return QTabletEvent::UnknownPointer; } QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t) { d << "TabletDevice id:" << t.uniqueId << " pressure: " << t.minPressure << ".." << t.maxPressure << " tan pressure: " << t.minTanPressure << ".." << t.maxTanPressure << " area:" << t.minX << t.minY << t.minZ << ".." << t.maxX << t.maxY << t.maxZ << " device " << t.currentDevice << " pointer " << t.currentPointerType; return d; } QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(const quint64 uniqueId, const UINT cursorType) const { QWindowsTabletDeviceData result; result.uniqueId = uniqueId; /* browse WinTab's many info items to discover pressure handling. */ AXIS axis; LOGCONTEXT lc; /* get the current context for its device variable. */ QWindowsTabletSupport::m_winTab32DLL.wTGet(m_context, &lc); if (KisTabletDebugger::instance()->initializationDebugEnabled()) { printContext(lc); } /* get the size of the pressure axis. */ QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_NPRESSURE, &axis); result.minPressure = int(axis.axMin); result.maxPressure = int(axis.axMax); QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_TPRESSURE, &axis); result.minTanPressure = int(axis.axMin); result.maxTanPressure = int(axis.axMax); LOGCONTEXT defaultLc; /* get default region */ QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFCONTEXT, 0, &defaultLc); result.maxX = int(defaultLc.lcInExtX) - int(defaultLc.lcInOrgX); result.maxY = int(defaultLc.lcInExtY) - int(defaultLc.lcInOrgY); result.maxZ = int(defaultLc.lcInExtZ) - int(defaultLc.lcInOrgZ); result.currentDevice = deviceType(cursorType); // Define a rectangle representing the whole screen as seen by Wintab. QRect qtDesktopRect = QApplication::desktop()->geometry(); QRect wintabDesktopRect(defaultLc.lcSysOrgX, defaultLc.lcSysOrgY, defaultLc.lcSysExtX, defaultLc.lcSysExtY); qDebug() << ppVar(qtDesktopRect); qDebug() << ppVar(wintabDesktopRect); // Show screen choice dialog if (!dialogOpen) { KisScreenSizeChoiceDialog dlg(0, wintabDesktopRect, qtDesktopRect); KisExtendedModifiersMapper mapper; KisExtendedModifiersMapper::ExtendedModifiers modifiers = mapper.queryExtendedModifiers(); if (modifiers.contains(Qt::Key_Shift) || (!dlg.canUseDefaultSettings() && qtDesktopRect != wintabDesktopRect)) { dialogOpen = true; dlg.exec(); } result.virtualDesktopArea = dlg.screenRect(); dialogOpen = false; } else { // this branch is really improbable and most probably means // a bug in the tablet driver. Anyway, we just shouldn't show // hundreds of tablet initialization dialogs result.virtualDesktopArea = qtDesktopRect; } return result; }; bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, LPARAM lParam) { auto sendProximityEvent = [&](QEvent::Type type) { QPointF emptyPos; qreal zero = 0.0; QTabletEvent e(type, emptyPos, emptyPos, 0, m_devices.at(m_currentDevice).currentPointerType, zero, 0, 0, zero, zero, 0, Qt::NoModifier, m_devices.at(m_currentDevice).uniqueId, Qt::NoButton, (Qt::MouseButtons)0); qApp->sendEvent(qApp, &e); }; if (!LOWORD(lParam)) { // dbgTablet << "leave proximity for device #" << m_currentDevice; sendProximityEvent(QEvent::TabletLeaveProximity); return true; } PACKET proximityBuffer[1]; // we are only interested in the first packet in this case const int totalPacks = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, 1, proximityBuffer); if (!totalPacks) return false; UINT pkCursor = proximityBuffer[0].pkCursor; // initializing and updating the cursor should be done in response to // WT_CSRCHANGE. We do it in WT_PROXIMITY because some wintab never send // the event WT_CSRCHANGE even if asked with CXO_CSRMESSAGES tabletUpdateCursor(pkCursor); // dbgTablet << "enter proximity for device #" << m_currentDevice << m_devices.at(m_currentDevice); sendProximityEvent(QEvent::TabletEnterProximity); return true; } bool QWindowsTabletSupport::translateTabletPacketEvent() { static PACKET localPacketBuf[TabletPacketQSize]; // our own tablet packet queue. const int packetCount = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, TabletPacketQSize, &localPacketBuf); if (!packetCount || m_currentDevice < 0 || dialogOpen) return false; // In contrast to Qt, these will not be "const" during our loop. // This is because the Surface Pro 3 may cause us to switch devices. QWindowsTabletDeviceData tabletData = m_devices.at(m_currentDevice); int currentDevice = tabletData.currentDevice; int currentPointerType = tabletData.currentPointerType; // static Qt::MouseButtons buttons = Qt::NoButton, btnOld, btnChange; static DWORD btnNew, btnOld, btnChange; // The tablet can be used in 2 different modes, depending on its settings: // 1) Absolute (pen) mode: // The coordinates are scaled to the virtual desktop (by default). The user // can also choose to scale to the monitor or a region of the screen. // When entering proximity, the tablet driver snaps the mouse pointer to the // tablet position scaled to that area and keeps it in sync. // 2) Relative (mouse) mode: // The pen follows the mouse. The constant 'absoluteRange' specifies the // manhattanLength difference for detecting if a tablet input device is in this mode, // in which case we snap the position to the mouse position. // It seems there is no way to find out the mode programmatically, the LOGCONTEXT orgX/Y/Ext // area is always the virtual desktop. static qreal dpr = 1.0; auto activeWindow = qApp->activeWindow(); if (activeWindow) { dpr = activeWindow->devicePixelRatio(); } const Qt::KeyboardModifiers keyboardModifiers = QApplication::queryKeyboardModifiers(); for (int i = 0; i < packetCount; ++i) { const PACKET &packet = localPacketBuf[i]; btnOld = btnNew; btnNew = localPacketBuf[i].pkButtons; btnChange = btnOld ^ btnNew; bool buttonPressed = btnChange && btnNew > btnOld; bool buttonReleased = btnChange && btnNew < btnOld; bool anyButtonsStillPressed = btnNew; Qt::MouseButton button = Qt::NoButton; Qt::MouseButtons buttons; globalButtonsConverter->convert(btnOld, btnNew, &button, &buttons, tabletData); QEvent::Type type = QEvent::TabletMove; if (buttonPressed && button != Qt::NoButton) { type = QEvent::TabletPress; } else if (buttonReleased && button != Qt::NoButton) { type = QEvent::TabletRelease; } const int z = currentDevice == QTabletEvent::FourDMouse ? int(packet.pkZ) : 0; // This code is to delay the tablet data one cycle to sync with the mouse location. QPointF globalPosF = m_oldGlobalPosF / dpr; // Convert from "native" to "device independent pixels." m_oldGlobalPosF = tabletData.scaleCoordinates(packet.pkX, packet.pkY, tabletData.virtualDesktopArea); QPoint globalPos = globalPosF.toPoint(); // Find top-level window QWidget *w = targetWindow; // If we had a target already, use it. if (!w) { w = qApp->activePopupWidget(); if (!w) w = qApp->activeModalWidget(); if (!w) w = qApp->topLevelAt(globalPos); if (!w) continue; w = w->window(); } const QPoint localPos = w->mapFromGlobal(globalPos); const QPointF delta = globalPosF - globalPos; const QPointF localPosF = globalPosF + QPointF(localPos - globalPos) + delta; const qreal pressureNew = packet.pkButtons && (currentPointerType == QTabletEvent::Pen || currentPointerType == QTabletEvent::Eraser) ? m_devices.at(m_currentDevice).scalePressure(packet.pkNormalPressure) : qreal(0); const qreal tangentialPressure = currentDevice == QTabletEvent::Airbrush ? m_devices.at(m_currentDevice).scaleTangentialPressure(packet.pkTangentPressure) : qreal(0); int tiltX = 0; int tiltY = 0; qreal rotation = 0; if (m_tiltSupport) { // Convert from azimuth and altitude to x tilt and y tilt. What // follows is the optimized version. Here are the equations used: // X = sin(azimuth) * cos(altitude) // Y = cos(azimuth) * cos(altitude) // Z = sin(altitude) // X Tilt = arctan(X / Z) // Y Tilt = arctan(Y / Z) const double radAzim = (packet.pkOrientation.orAzimuth / 10.0) * (M_PI / 180); const double tanAlt = std::tan((std::abs(packet.pkOrientation.orAltitude / 10.0)) * (M_PI / 180)); const double degX = std::atan(std::sin(radAzim) / tanAlt); const double degY = std::atan(std::cos(radAzim) / tanAlt); tiltX = int(degX * (180 / M_PI)); tiltY = int(-degY * (180 / M_PI)); rotation = 360.0 - (packet.pkOrientation.orTwist / 10.0); if (rotation > 180.0) rotation -= 360.0; } // This is adds *a lot* of noise to the output log if (false) { dbgTablet << "Packet #" << (i+1) << '/' << packetCount << "button:" << packet.pkButtons << globalPosF << z << "to:" << w << localPos << "(packet" << packet.pkX << packet.pkY << ") dev:" << currentDevice << "pointer:" << currentPointerType << "P:" << pressureNew << "tilt:" << tiltX << ',' << tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation; } // Reusable helper function. Better than compiler macros! auto sendTabletEvent = [&](QTabletEvent::Type t){ handleTabletEvent(w, localPosF, globalPosF, currentDevice, currentPointerType, button, buttons, pressureNew, tiltX, tiltY, tangentialPressure, rotation, z, m_devices.at(m_currentDevice).uniqueId, keyboardModifiers, t, packet.pkTime); }; /** * Workaround to deal with "inline" tool switches. * These are caused by the eraser trigger button on the Surface Pro 3. * We shoot out a tabletUpdateCursor request and a switchInputDevice request. */ if (isSurfacePro3 && (packet.pkCursor != currentPkCursor)) { dbgTablet << "Got an inline tool switch."; // Send tablet release event. sendTabletEvent(QTabletEvent::TabletRelease); // Read the new cursor info. UINT pkCursor = packet.pkCursor; tabletUpdateCursor(pkCursor); // Update the local loop variables. tabletData = m_devices.at(m_currentDevice); currentDevice = deviceType(tabletData.currentDevice); currentPointerType = pointerType(pkCursor); sendTabletEvent(QTabletEvent::TabletPress); } sendTabletEvent(type); } // Loop over packets return true; } void QWindowsTabletSupport::tabletUpdateCursor(const int pkCursor) { UINT physicalCursorId; QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + pkCursor, CSR_PHYSID, &physicalCursorId); UINT cursorType; QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + pkCursor, CSR_TYPE, &cursorType); const qint64 uniqueId = (qint64(cursorType & DeviceIdMask) << 32L) | qint64(physicalCursorId); m_currentDevice = indexOfDevice(m_devices, uniqueId); if (m_currentDevice < 0) { m_currentDevice = m_devices.size(); m_devices.push_back(tabletInit(uniqueId, cursorType)); // Note: ideally we might check this button map for changes every // update. However there seems to be an issue with Wintab altering the // button map when the user right-clicks in Krita while another // application has focus. This forces Krita to load button settings only // once, when the tablet is first detected. // // See https://bugs.kde.org/show_bug.cgi?id=359561 BYTE logicalButtons[32]; memset(logicalButtons, 0, 32); m_winTab32DLL.wTInfo(WTI_CURSORS + pkCursor, CSR_SYSBTNMAP, &logicalButtons); m_devices[m_currentDevice].buttonsMap[0x1] = logicalButtons[0]; m_devices[m_currentDevice].buttonsMap[0x2] = logicalButtons[1]; m_devices[m_currentDevice].buttonsMap[0x4] = logicalButtons[2]; } m_devices[m_currentDevice].currentPointerType = pointerType(pkCursor); currentPkCursor = pkCursor; // Check tablet name to enable Surface Pro 3 workaround. #ifdef UNICODE if (!isSurfacePro3) { /** * Some really "nice" tablet drivers don't know that trhey are * supposed to return their name length when the buffer is * null and they try to write into it effectively causing a * suicide. So we cannot rely on it :( * * We workaround it by just allocating a big array and hoping * for the best. * * Failing tablets: * - Adesso Cybertablet M14 * - Peritab-302 * - Trust Tablet TB7300 * - VisTablet Realm Pro * - Aiptek 14000u (latest driver: v5.03, 2013-10-21) * - Genius G-Pen F350 * - Genius G-Pen 560 (supported on win7 only) */ // we cannot use the correct api :( // UINT nameLength = QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_NAME, 0); // 1024 chars should be enough for everyone! (c) UINT nameLength = 1024; TCHAR* dvcName = new TCHAR[nameLength + 1]; memset(dvcName, 0, sizeof(TCHAR) * nameLength); UINT writtenBytes = QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_NAME, dvcName); if (writtenBytes > sizeof(TCHAR) * nameLength) { qWarning() << "WINTAB WARNING: tablet name is too long!" << writtenBytes; // avoid crash when trying to read it dvcName[nameLength - 1] = (TCHAR)0; } QString qDvcName = QString::fromWCharArray((const wchar_t*)dvcName); dbgInput << "DVC_NAME =" << qDvcName; // Name changed between older and newer Surface Pro 3 drivers if (qDvcName == QString::fromLatin1("N-trig DuoSense device") || qDvcName == QString::fromLatin1("Microsoft device")) { dbgInput << "This looks like a Surface Pro 3. Enabling eraser workaround."; isSurfacePro3 = true; } delete[] dvcName; } #endif } QT_END_NAMESPACE diff --git a/libs/ui/input/wintab/qxcbconnection_xi2.cpp b/libs/ui/input/wintab/qxcbconnection_xi2.cpp index 78a9ea1829..e0604be0dc 100644 --- a/libs/ui/input/wintab/qxcbconnection_xi2.cpp +++ b/libs/ui/input/wintab/qxcbconnection_xi2.cpp @@ -1,1041 +1,1045 @@ /**************************************************************************** ** ** 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 "qxcbconnection.h" //#include "qxcbkeyboard.h" //#include "qxcbscreen.h" //#include "qxcbwindow.h" //#include "qtouchdevice.h" //#include #include #include #include #ifdef XCB_USE_XINPUT2 #include #include struct XInput2TouchDeviceData { XInput2TouchDeviceData() - : xiDeviceInfo(0) - , qtTouchDevice(0) + : xiDeviceInfo(0) + , qtTouchDevice(0) { } XIDeviceInfo *xiDeviceInfo; QTouchDevice *qtTouchDevice; QHash touchPoints; // Stuff that is relevant only for touchpads QHash pointPressedPosition; // in screen coordinates where each point was pressed QPointF firstPressedPosition; // in screen coordinates where the first point was pressed QPointF firstPressedNormalPosition; // device coordinates (0 to 1, 0 to 1) where the first point was pressed QSizeF size; // device size in mm }; void QXcbConnection::initializeXInput2() { // TODO Qt 6 (or perhaps earlier): remove these redundant env variables if (qEnvironmentVariableIsSet("KIS_QT_XCB_DEBUG_XINPUT")) const_cast(lcQpaXInput()).setEnabled(QtDebugMsg, true); if (qEnvironmentVariableIsSet("KIS_QT_XCB_DEBUG_XINPUT_DEVICES")) const_cast(lcQpaXInputDevices()).setEnabled(QtDebugMsg, true); Display *xDisplay = static_cast(m_xlib_display); if (XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) { int xiMajor = 2; m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) { m_xi2Minor = 1; // for smooth scrolling 2.1 is enough if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) { m_xi2Minor = 0; // for tablet support 2.0 is enough m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest; } else m_xi2Enabled = true; } else m_xi2Enabled = true; if (m_xi2Enabled) { #ifdef XCB_USE_XINPUT22 qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.2 or greater", xiMajor, m_xi2Minor); #else qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.0", xiMajor, m_xi2Minor); #endif } xi2SetupDevices(); } } void QXcbConnection::xi2SetupDevices() { #ifndef QT_NO_TABLETEVENT m_tabletData.clear(); #endif m_scrollingDevices.clear(); if (!m_xi2Enabled) return; Display *xDisplay = static_cast(m_xlib_display); int deviceCount = 0; XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount); for (int i = 0; i < deviceCount; ++i) { // Only non-master pointing devices are relevant here. if (devices[i].use != XISlavePointer) continue; qCDebug(lcQpaXInputDevices) << "input device "<< devices[i].name; #ifndef QT_NO_TABLETEVENT TabletData tabletData; #endif ScrollingDevice scrollingDevice; for (int c = 0; c < devices[i].num_classes; ++c) { switch (devices[i].classes[c]->type) { case XIValuatorClass: { XIValuatorClassInfo *vci = reinterpret_cast(devices[i].classes[c]); const int valuatorAtom = qatom(vci->label); qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms); #ifndef QT_NO_TABLETEVENT if (valuatorAtom < QXcbAtom::NAtoms) { TabletData::ValuatorClassInfo info; info.minVal = vci->min; info.maxVal = vci->max; info.number = vci->number; tabletData.valuatorInfo[valuatorAtom] = info; } #endif // QT_NO_TABLETEVENT if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) scrollingDevice.lastScrollPosition.setX(vci->value); else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) scrollingDevice.lastScrollPosition.setY(vci->value); break; } #ifdef XCB_USE_XINPUT21 case XIScrollClass: { XIScrollClassInfo *sci = reinterpret_cast(devices[i].classes[c]); if (sci->scroll_type == XIScrollTypeVertical) { scrollingDevice.orientations |= Qt::Vertical; scrollingDevice.verticalIndex = sci->number; scrollingDevice.verticalIncrement = sci->increment; } else if (sci->scroll_type == XIScrollTypeHorizontal) { scrollingDevice.orientations |= Qt::Horizontal; scrollingDevice.horizontalIndex = sci->number; scrollingDevice.horizontalIncrement = sci->increment; } break; } case XIButtonClass: { XIButtonClassInfo *bci = reinterpret_cast(devices[i].classes[c]); if (bci->num_buttons >= 5) { Atom label4 = bci->labels[3]; Atom label5 = bci->labels[4]; // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons. if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) && - (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown)) + (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown)) scrollingDevice.legacyOrientations |= Qt::Vertical; } if (bci->num_buttons >= 7) { Atom label6 = bci->labels[5]; Atom label7 = bci->labels[6]; if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight)) scrollingDevice.legacyOrientations |= Qt::Horizontal; } qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons); break; } #endif case XIKeyClass: qCDebug(lcQpaXInputDevices) << " it's a keyboard"; break; #ifdef XCB_USE_XINPUT22 case XITouchClass: // will be handled in deviceForId() break; #endif default: qCDebug(lcQpaXInputDevices) << " has class" << devices[i].classes[c]->type; break; } } bool isTablet = false; #ifndef QT_NO_TABLETEVENT // If we have found the valuators which we expect a tablet to have, it might be a tablet. if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) && tabletData.valuatorInfo.contains(QXcbAtom::AbsY) && tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure)) isTablet = true; // But we need to be careful not to take the touch and tablet-button devices as tablets. QByteArray name = QByteArray(devices[i].name).toLower(); QString dbgType = QLatin1String("UNKNOWN"); if (name.contains("eraser")) { isTablet = true; tabletData.pointerType = QTabletEvent::Eraser; dbgType = QLatin1String("eraser"); } else if (name.contains("cursor")) { isTablet = true; tabletData.pointerType = QTabletEvent::Cursor; dbgType = QLatin1String("cursor"); } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) { tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("wacom") && isTablet && !name.contains("touch")) { // combined device (evdev) rather than separate pen/eraser (wacom driver) tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) { // some "Genius" tablets isTablet = true; tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("waltop") && name.contains("tablet")) { // other "Genius" tablets // WALTOP International Corp. Slim Tablet isTablet = true; tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); } else if (name.contains("uc-logic")) { isTablet = true; tabletData.pointerType = QTabletEvent::Pen; dbgType = QLatin1String("pen"); + } else if (name.contains("ugee")) { + isTablet = true; + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); } else { isTablet = false; } if (isTablet) { tabletData.deviceId = devices[i].deviceid; m_tabletData.append(tabletData); qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType; } #endif // QT_NO_TABLETEVENT #ifdef XCB_USE_XINPUT21 if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) { scrollingDevice.deviceId = devices[i].deviceid; // Only use legacy wheel button events when we don't have real scroll valuators. scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations; m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice); qCDebug(lcQpaXInputDevices) << " it's a scrolling device"; } #endif if (!isTablet) { // touchDeviceForId populates XInput2DeviceData the first time it is called // with a new deviceId. On subsequent calls it will return the cached object. XInput2TouchDeviceData *dev = touchDeviceForId(devices[i].deviceid); if (dev && lcQpaXInputDevices().isDebugEnabled()) { if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen) qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d", dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), dev->qtTouchDevice->maximumTouchPoints()); else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad) qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f", dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), dev->qtTouchDevice->maximumTouchPoints(), dev->size.width(), dev->size.height()); } } } XIFreeDeviceInfo(devices); } void QXcbConnection::finalizeXInput2() { foreach (XInput2TouchDeviceData *dev, m_touchDevices) { if (dev->xiDeviceInfo) XIFreeDeviceInfo(dev->xiDeviceInfo); delete dev->qtTouchDevice; delete dev; } } void QXcbConnection::xi2Select(xcb_window_t window) { if (!m_xi2Enabled) return; Display *xDisplay = static_cast(m_xlib_display); unsigned int bitMask = 0; unsigned char *xiBitMask = reinterpret_cast(&bitMask); #ifdef XCB_USE_XINPUT22 if (isAtLeastXI22()) { bitMask |= XI_TouchBeginMask; bitMask |= XI_TouchUpdateMask; bitMask |= XI_TouchEndMask; bitMask |= XI_PropertyEventMask; // for tablets if (xi2MouseEvents()) { // We want both mouse and touch through XI2 if touch is supported (>= 2.2). // The plain xcb press and motion events will not be delivered after this. bitMask |= XI_ButtonPressMask; bitMask |= XI_ButtonReleaseMask; bitMask |= XI_MotionMask; qCDebug(lcQpaXInput, "XInput 2.2: Selecting press/release/motion events in addition to touch"); } XIEventMask mask; mask.mask_len = sizeof(bitMask); mask.mask = xiBitMask; // When xi2MouseEvents() is true (the default), pointer emulation for touch and tablet // events will get disabled. This is preferable for touch, as Qt Quick handles touch events // directly while for others QtGui synthesizes mouse events, not so much for tablets. For // the latter we will synthesize the events ourselves. mask.deviceid = XIAllMasterDevices; Status result = XISelectEvents(xDisplay, window, &mask, 1); if (result != Success) qCDebug(lcQpaXInput, "XInput 2.2: failed to select pointer/touch events, window %x, result %d", window, result); } #endif // XCB_USE_XINPUT22 const bool pointerSelected = isAtLeastXI22() && xi2MouseEvents(); QSet tabletDevices; #ifndef QT_NO_TABLETEVENT if (!m_tabletData.isEmpty()) { unsigned int tabletBitMask; unsigned char *xiTabletBitMask = reinterpret_cast(&tabletBitMask); QVector xiEventMask(m_tabletData.count()); tabletBitMask = XI_PropertyEventMask; if (!pointerSelected) tabletBitMask |= XI_ButtonPressMask | XI_ButtonReleaseMask | XI_MotionMask; for (int i = 0; i < m_tabletData.count(); ++i) { int deviceId = m_tabletData.at(i).deviceId; tabletDevices.insert(deviceId); xiEventMask[i].deviceid = deviceId; xiEventMask[i].mask_len = sizeof(tabletBitMask); xiEventMask[i].mask = xiTabletBitMask; } XISelectEvents(xDisplay, window, xiEventMask.data(), m_tabletData.count()); } #endif // QT_NO_TABLETEVENT #ifdef XCB_USE_XINPUT21 // Enable each scroll device if (!m_scrollingDevices.isEmpty() && !pointerSelected) { // Only when XI2 mouse events are not enabled, otherwise motion and release are selected already. QVector xiEventMask(m_scrollingDevices.size()); unsigned int scrollBitMask; unsigned char *xiScrollBitMask = reinterpret_cast(&scrollBitMask); scrollBitMask = XI_MotionMask; scrollBitMask |= XI_ButtonReleaseMask; int i=0; Q_FOREACH (const ScrollingDevice& scrollingDevice, m_scrollingDevices) { if (tabletDevices.contains(scrollingDevice.deviceId)) continue; // All necessary events are already captured. xiEventMask[i].deviceid = scrollingDevice.deviceId; xiEventMask[i].mask_len = sizeof(scrollBitMask); xiEventMask[i].mask = xiScrollBitMask; i++; } XISelectEvents(xDisplay, window, xiEventMask.data(), i); } #else Q_UNUSED(xiBitMask); #endif { // Listen for hotplug events XIEventMask xiEventMask; bitMask = XI_HierarchyChangedMask; bitMask |= XI_DeviceChangedMask; xiEventMask.deviceid = XIAllDevices; xiEventMask.mask_len = sizeof(bitMask); xiEventMask.mask = xiBitMask; XISelectEvents(xDisplay, window, &xiEventMask, 1); } } XInput2TouchDeviceData *QXcbConnection::touchDeviceForId(int id) { XInput2TouchDeviceData *dev = 0; QHash::const_iterator devIt = m_touchDevices.constFind(id); if (devIt != m_touchDevices.cend()) { dev = devIt.value(); } else { int nrDevices = 0; QTouchDevice::Capabilities caps = 0; dev = new XInput2TouchDeviceData; dev->xiDeviceInfo = XIQueryDevice(static_cast(m_xlib_display), id, &nrDevices); if (nrDevices <= 0) { delete dev; return 0; } int type = -1; int maxTouchPoints = 1; bool hasRelativeCoords = false; for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) { XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i]; switch (classinfo->type) { #ifdef XCB_USE_XINPUT22 case XITouchClass: { XITouchClassInfo *tci = reinterpret_cast(classinfo); maxTouchPoints = tci->num_touches; qCDebug(lcQpaXInputDevices, " has touch class with mode %d", tci->mode); switch (tci->mode) { case XIDependentTouch: type = QTouchDevice::TouchPad; break; case XIDirectTouch: type = QTouchDevice::TouchScreen; break; } break; } #endif // XCB_USE_XINPUT22 case XIValuatorClass: { XIValuatorClassInfo *vci = reinterpret_cast(classinfo); if (vci->label == atom(QXcbAtom::AbsMTPositionX)) caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition; else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor)) caps |= QTouchDevice::Area; else if (vci->label == atom(QXcbAtom::AbsMTPressure) || vci->label == atom(QXcbAtom::AbsPressure)) caps |= QTouchDevice::Pressure; else if (vci->label == atom(QXcbAtom::RelX)) { hasRelativeCoords = true; dev->size.setWidth((vci->max - vci->min) * 1000.0 / vci->resolution); } else if (vci->label == atom(QXcbAtom::RelY)) { hasRelativeCoords = true; dev->size.setHeight((vci->max - vci->min) * 1000.0 / vci->resolution); } else if (vci->label == atom(QXcbAtom::AbsX)) { dev->size.setHeight((vci->max - vci->min) * 1000.0 / vci->resolution); } else if (vci->label == atom(QXcbAtom::AbsY)) { dev->size.setWidth((vci->max - vci->min) * 1000.0 / vci->resolution); } break; } default: break; } } if (type < 0 && caps && hasRelativeCoords) { type = QTouchDevice::TouchPad; if (dev->size.width() < 10 || dev->size.height() < 10 || dev->size.width() > 10000 || dev->size.height() > 10000) dev->size = QSizeF(130, 110); } -// WARNING: Krita edit -// if (!isAtLeastXI22() || type == QTouchDevice::TouchPad) -// caps |= QTouchDevice::MouseEmulation; + // WARNING: Krita edit + // if (!isAtLeastXI22() || type == QTouchDevice::TouchPad) + // caps |= QTouchDevice::MouseEmulation; if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) { dev->qtTouchDevice = new QTouchDevice; dev->qtTouchDevice->setName(QString::fromUtf8(dev->xiDeviceInfo->name)); dev->qtTouchDevice->setType((QTouchDevice::DeviceType)type); dev->qtTouchDevice->setCapabilities(caps); dev->qtTouchDevice->setMaximumTouchPoints(maxTouchPoints); if (caps != 0) QWindowSystemInterface::registerTouchDevice(dev->qtTouchDevice); m_touchDevices[id] = dev; } else { XIFreeDeviceInfo(dev->xiDeviceInfo); delete dev; dev = 0; } } return dev; } #if defined(XCB_USE_XINPUT21) || !defined(QT_NO_TABLETEVENT) static inline qreal fixed1616ToReal(FP1616 val) { return qreal(val) / 0x10000; } #endif // defined(XCB_USE_XINPUT21) || !defined(QT_NO_TABLETEVENT) bool QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) { if (xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) { xXIGenericDeviceEvent *xiEvent = reinterpret_cast(event); int sourceDeviceId = xiEvent->deviceid; // may be the master id xXIDeviceEvent *xiDeviceEvent = 0; QWindow *window = 0; switch (xiEvent->evtype) { case XI_ButtonPress: case XI_ButtonRelease: case XI_Motion: #ifdef XCB_USE_XINPUT22 case XI_TouchBegin: case XI_TouchUpdate: case XI_TouchEnd: #endif { xiDeviceEvent = reinterpret_cast(event); window = windowFromId(xiDeviceEvent->event); sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master break; } case XI_HierarchyChanged: xi2HandleHierachyEvent(xiEvent); return false; case XI_DeviceChanged: xi2HandleDeviceChangedEvent(xiEvent); return false; default: break; } #ifndef QT_NO_TABLETEVENT for (int i = 0; i < m_tabletData.count(); ++i) { if (m_tabletData.at(i).deviceId == sourceDeviceId) { if (xi2HandleTabletEvent(xiEvent, &m_tabletData[i], window)) { return true; } } } #endif // QT_NO_TABLETEVENT #ifdef XCB_USE_XINPUT21 QHash::iterator device = m_scrollingDevices.find(sourceDeviceId); if (device != m_scrollingDevices.end()) xi2HandleScrollEvent(xiEvent, device.value()); // Removed from Qt... #endif // XCB_USE_XINPUT21 #ifdef XCB_USE_XINPUT22 // Removed from Qt... #endif // XCB_USE_XINPUT22 } return false; } #ifdef XCB_USE_XINPUT22 bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) { if (grab && !canGrab()) return false; int num_devices = 0; Display *xDisplay = static_cast(xlib_display()); XIDeviceInfo *info = XIQueryDevice(xDisplay, XIAllMasterDevices, &num_devices); if (!info) return false; XIEventMask evmask; unsigned char mask[XIMaskLen(XI_LASTEVENT)]; evmask.mask = mask; evmask.mask_len = sizeof(mask); memset(mask, 0, sizeof(mask)); evmask.deviceid = XIAllMasterDevices; XISetMask(mask, XI_ButtonPress); XISetMask(mask, XI_ButtonRelease); XISetMask(mask, XI_Motion); XISetMask(mask, XI_TouchBegin); XISetMask(mask, XI_TouchUpdate); XISetMask(mask, XI_TouchEnd); bool grabbed = true; for (int i = 0; i < num_devices; i++) { int id = info[i].deviceid, n = 0; XIDeviceInfo *deviceInfo = XIQueryDevice(xDisplay, id, &n); if (deviceInfo) { const bool grabbable = deviceInfo->use != XIMasterKeyboard; XIFreeDeviceInfo(deviceInfo); if (!grabbable) continue; } if (!grab) { Status result = XIUngrabDevice(xDisplay, id, CurrentTime); if (result != Success) { grabbed = false; qCDebug(lcQpaXInput, "XInput 2.2: failed to ungrab events for device %d (result %d)", id, result); } } else { Status result = XIGrabDevice(xDisplay, id, w, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &evmask); if (result != Success) { grabbed = false; qCDebug(lcQpaXInput, "XInput 2.2: failed to grab events for device %d on window %x (result %d)", id, w, result); } } } XIFreeDeviceInfo(info); m_xiGrab = grabbed; return grabbed; } #endif // XCB_USE_XINPUT22 void QXcbConnection::xi2HandleHierachyEvent(void *event) { xXIHierarchyEvent *xiEvent = reinterpret_cast(event); // We only care about hotplugged devices if (!(xiEvent->flags & (XISlaveRemoved | XISlaveAdded))) return; xi2SetupDevices(); // Reselect events for all event-listening windows. Q_FOREACH (xcb_window_t window, m_windowMapper.keys()) { xi2Select(window); } } void QXcbConnection::xi2HandleDeviceChangedEvent(void *event) { xXIDeviceChangedEvent *xiEvent = reinterpret_cast(event); // ### If a slave device changes (XIDeviceChange), we should probably run setup on it again. if (xiEvent->reason != XISlaveSwitch) return; #ifdef XCB_USE_XINPUT21 // This code handles broken scrolling device drivers that reset absolute positions // when they are made active. Whenever a new slave device is made active the // primary pointer sends a DeviceChanged event with XISlaveSwitch, and the new // active slave in sourceid. QHash::iterator device = m_scrollingDevices.find(xiEvent->sourceid); if (device == m_scrollingDevices.end()) return; int nrDevices = 0; XIDeviceInfo* xiDeviceInfo = XIQueryDevice(static_cast(m_xlib_display), xiEvent->sourceid, &nrDevices); if (nrDevices <= 0) { qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", xiEvent->sourceid); return; } updateScrollingDevice(*device, xiDeviceInfo->num_classes, xiDeviceInfo->classes); XIFreeDeviceInfo(xiDeviceInfo); #endif } void QXcbConnection::updateScrollingDevice(ScrollingDevice &scrollingDevice, int num_classes, void *classInfo) { #ifdef XCB_USE_XINPUT21 XIAnyClassInfo **classes = reinterpret_cast(classInfo); QPointF lastScrollPosition; if (lcQpaXInput().isDebugEnabled()) lastScrollPosition = scrollingDevice.lastScrollPosition; for (int c = 0; c < num_classes; ++c) { if (classes[c]->type == XIValuatorClass) { XIValuatorClassInfo *vci = reinterpret_cast(classes[c]); const int valuatorAtom = qatom(vci->label); if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) scrollingDevice.lastScrollPosition.setX(vci->value); else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) scrollingDevice.lastScrollPosition.setY(vci->value); } } if (lcQpaXInput().isDebugEnabled() && lastScrollPosition != scrollingDevice.lastScrollPosition) qCDebug(lcQpaXInput, "scrolling device %d moved from (%f, %f) to (%f, %f)", scrollingDevice.deviceId, lastScrollPosition.x(), lastScrollPosition.y(), scrollingDevice.lastScrollPosition.x(), scrollingDevice.lastScrollPosition.y()); #else Q_UNUSED(scrollingDevice); Q_UNUSED(num_classes); Q_UNUSED(classInfo); #endif } Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b) { switch (b) { case 1: return Qt::LeftButton; case 2: return Qt::MiddleButton; case 3: return Qt::RightButton; - // 4-7 are for scrolling + // 4-7 are for scrolling default: break; } if (b >= 8 && b <= Qt::MaxMouseButton) return static_cast(Qt::BackButton << (b - 8)); return Qt::NoButton; } Qt::MouseButtons QXcbConnection::xiToQtMouseButtons(xXIDeviceEvent *xiDeviceEvent) { /** * WARNING: we haven't tested this method on different tablets. For basic tablets * without any button remapping it seems to work. For more complex configurations * I just don't know. Right now it is only safe to check if buttons is * equal to Qt::NoButton! */ int len = xiDeviceEvent->buttons_len; const uint32_t *buttons = reinterpret_cast(&xiDeviceEvent[1]);; Qt::MouseButtons qtbuttons = Qt::NoButton; const int numBits = len * 32; for (int i = 1; i < numBits; i++) { const int index = (i) / 32; const int offset = (i) % 32; const bool isActive = buttons[index] & (1 << offset); if (isActive) { qtbuttons |= xiToQtMouseButton(i); } } return qtbuttons; } static QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) { // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c switch (toolId) { case 0xd12: case 0x912: case 0x112: case 0x913: /* Intuos3 Airbrush */ case 0x91b: /* Intuos3 Airbrush Eraser */ case 0x902: /* Intuos4/5 13HD/24HD Airbrush */ case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */ case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */ case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */ return QTabletEvent::Airbrush; case 0x007: /* Mouse 4D and 2D */ case 0x09c: case 0x094: return QTabletEvent::FourDMouse; case 0x017: /* Intuos3 2D Mouse */ case 0x806: /* Intuos4 Mouse */ case 0x096: /* Lens cursor */ case 0x097: /* Intuos3 Lens cursor */ case 0x006: /* Intuos4 Lens cursor */ return QTabletEvent::Puck; case 0x885: /* Intuos3 Art Pen (Marker Pen) */ case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */ case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */ return QTabletEvent::RotationStylus; case 0: return QTabletEvent::NoDevice; } return QTabletEvent::Stylus; // Safe default assumption if nonzero } #ifndef QT_NO_TABLETEVENT bool QXcbConnection::xi2HandleTabletEvent(void *event, TabletData *tabletData, QWindow *window) { bool handled = true; Display *xDisplay = static_cast(m_xlib_display); xXIGenericDeviceEvent *xiEvent = static_cast(event); xXIDeviceEvent *xiDeviceEvent = reinterpret_cast(xiEvent); /** * On some systems we can lose tablet button release event if the tablet * was "closing a popup window by clicking somewhere outside the app * window". It means that we get a tablet press event, but get absolutely * no tablet release event. That can cause quite a lot of troubles, so here we * check if reported button state is consistent and if not, just reset it. * * WARNING: We haven't tested xiToQtMouseButtons() functions on all the * tablet models and configurations, so at the moment we rely only * on its Qt::NoButton state. If people test it on custom tablet * button configurations, we can just stop tracking * tabletData->buttons and use this mapping instead. */ if (xiEvent->evtype == XI_Motion || - xiEvent->evtype == XI_ButtonPress || - xiEvent->evtype == XI_ButtonRelease) { + xiEvent->evtype == XI_ButtonPress || + xiEvent->evtype == XI_ButtonRelease) { Qt::MouseButtons expectedButtons = xiToQtMouseButtons(xiDeviceEvent); if (expectedButtons != tabletData->buttons) { if (expectedButtons == Qt::NoButton) { tabletData->buttons = expectedButtons; } else { qWarning() << "==="; qWarning() << "WARNING: Tracked tablet buttons are not consistent!"; qWarning() << " " << ppVar(tabletData->buttons); qWarning() << " " << ppVar(expectedButtons); } } } switch (xiEvent->evtype) { case XI_ButtonPress: { Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail); tabletData->buttons |= b; xi2ReportTabletEvent(*tabletData, xiEvent); break; } case XI_ButtonRelease: { Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail); tabletData->buttons ^= b; xi2ReportTabletEvent(*tabletData, xiEvent); break; } case XI_Motion: xi2ReportTabletEvent(*tabletData, xiEvent); break; case XI_PropertyEvent: { // This is the wacom driver's way of reporting tool proximity. // The evdev driver doesn't do it this way. xXIPropertyEvent *ev = reinterpret_cast(event); if (ev->what == XIPropertyModified) { if (ev->property == atom(QXcbAtom::WacomSerialIDs)) { enum WacomSerialIndex { _WACSER_USB_ID = 0, _WACSER_LAST_TOOL_SERIAL, _WACSER_LAST_TOOL_ID, _WACSER_TOOL_SERIAL, _WACSER_TOOL_ID, _WACSER_COUNT }; Atom propType; int propFormat; unsigned long numItems, bytesAfter; unsigned char *data; if (XIGetProperty(xDisplay, tabletData->deviceId, ev->property, 0, 100, 0, AnyPropertyType, &propType, &propFormat, &numItems, &bytesAfter, &data) == Success) { if (propType == atom(QXcbAtom::INTEGER) && propFormat == 32 && numItems == _WACSER_COUNT) { quint32 *ptr = reinterpret_cast(data); quint32 tool = ptr[_WACSER_TOOL_ID]; // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/ // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1 if (!tool && ptr[_WACSER_TOOL_SERIAL]) tool = ptr[_WACSER_TOOL_SERIAL]; // The property change event informs us which tool is in proximity or which one left proximity. if (tool) { tabletData->inProximity = true; tabletData->tool = toolIdToTabletDevice(tool); tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_TOOL_SERIAL]); QWindowSystemInterface::handleTabletEnterProximityEvent(tabletData->tool, tabletData->pointerType, tabletData->serialId); } else { tabletData->inProximity = false; tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_ID]); // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/ // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1 if (!tabletData->tool) tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_SERIAL]); tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_LAST_TOOL_SERIAL]); QWindowSystemInterface::handleTabletLeaveProximityEvent(tabletData->tool, tabletData->pointerType, tabletData->serialId); } // TODO maybe have a hash of tabletData->deviceId to device data so we can // look up the tablet name here, and distinguish multiple tablets qCDebug(lcQpaXInput, "XI2 proximity change on tablet %d (USB %x): last tool: %x id %x current tool: %x id %x TabletDevice %d", - tabletData->deviceId, ptr[_WACSER_USB_ID], ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID], - ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], tabletData->tool); + tabletData->deviceId, ptr[_WACSER_USB_ID], ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID], + ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], tabletData->tool); } XFree(data); } } } break; } default: handled = false; break; } #ifdef XCB_USE_XINPUT22 // Synthesize mouse events since otherwise there are no mouse events from // the pen on the XI 2.2+ path. if (xi2MouseEvents() && window) { // DK: I have no idea why this line was introduced in Qt5.5! //eventListener->handleXIMouseEvent(reinterpret_cast(event)); } #else Q_UNUSED(eventListener); #endif return handled; } inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize) { return screenMin + normValue * screenSize; } void QXcbConnection::xi2ReportTabletEvent(TabletData &tabletData, void *event) { xXIDeviceEvent *ev = reinterpret_cast(event); QWindow *window = windowFromId(ev->event); if (!window) return; const double scale = 65536.0; QPointF local(ev->event_x / scale, ev->event_y / scale); QPointF global(ev->root_x / scale, ev->root_y / scale); double pressure = 0, rotation = 0, tangentialPressure = 0; int xTilt = 0, yTilt = 0; QRect screenArea = qApp->desktop()->rect(); for (QHash::iterator it = tabletData.valuatorInfo.begin(), - ite = tabletData.valuatorInfo.end(); it != ite; ++it) { + ite = tabletData.valuatorInfo.end(); it != ite; ++it) { int valuator = it.key(); TabletData::ValuatorClassInfo &classInfo(it.value()); xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal); double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal); switch (valuator) { case QXcbAtom::AbsX: { const qreal value = scaleOneValuator(normalizedValue, screenArea.x(), screenArea.width()); const qreal offset = local.x() - global.x(); global.rx() = value; local.rx() = value + offset; break; } case QXcbAtom::AbsY: { qreal value = scaleOneValuator(normalizedValue, screenArea.y(), screenArea.height()); qreal offset = local.y() - global.y(); global.ry() = value; local.ry() = value + offset; break; } case QXcbAtom::AbsPressure: pressure = normalizedValue; break; case QXcbAtom::AbsTiltX: xTilt = classInfo.curVal; break; case QXcbAtom::AbsTiltY: yTilt = classInfo.curVal; break; case QXcbAtom::AbsWheel: switch (tabletData.tool) { case QTabletEvent::Airbrush: tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range break; case QTabletEvent::RotationStylus: rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees break; default: // Other types of styli do not use this valuator break; } break; default: break; } } if (Q_UNLIKELY(lcQpaXInput().isDebugEnabled())) qCDebug(lcQpaXInput, "XI2 event on tablet %d with tool %d type %d seq %d detail %d pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf", - tabletData.deviceId, tabletData.tool, ev->evtype, ev->sequenceNumber, ev->detail, - fixed1616ToReal(ev->event_x), fixed1616ToReal(ev->event_y), - fixed1616ToReal(ev->root_x), fixed1616ToReal(ev->root_y), - (int)tabletData.buttons, pressure, xTilt, yTilt, rotation); + tabletData.deviceId, tabletData.tool, ev->evtype, ev->sequenceNumber, ev->detail, + fixed1616ToReal(ev->event_x), fixed1616ToReal(ev->event_y), + fixed1616ToReal(ev->root_x), fixed1616ToReal(ev->root_y), + (int)tabletData.buttons, pressure, xTilt, yTilt, rotation); Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); QWindowSystemInterface::handleTabletEvent(window, local, global, tabletData.tool, tabletData.pointerType, tabletData.buttons, pressure, xTilt, yTilt, tangentialPressure, rotation, 0, tabletData.serialId, modifiers); } void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice) { #ifdef XCB_USE_XINPUT21 xXIGenericDeviceEvent *xiEvent = reinterpret_cast(event); if (xiEvent->evtype == XI_Motion && scrollingDevice.orientations) { xXIDeviceEvent* xiDeviceEvent = reinterpret_cast(event); if (QWindow *platformWindow = windowFromId(xiDeviceEvent->event)) { QPoint rawDelta; QPoint angleDelta; double value; if (scrollingDevice.orientations & Qt::Vertical) { if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.verticalIndex, &value)) { double delta = scrollingDevice.lastScrollPosition.y() - value; scrollingDevice.lastScrollPosition.setY(value); angleDelta.setY((delta / scrollingDevice.verticalIncrement) * 120); // We do not set "pixel" delta if it is only measured in ticks. if (scrollingDevice.verticalIncrement > 1) rawDelta.setY(delta); } } if (scrollingDevice.orientations & Qt::Horizontal) { if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.horizontalIndex, &value)) { double delta = scrollingDevice.lastScrollPosition.x() - value; scrollingDevice.lastScrollPosition.setX(value); angleDelta.setX((delta / scrollingDevice.horizontalIncrement) * 120); // We do not set "pixel" delta if it is only measured in ticks. if (scrollingDevice.horizontalIncrement > 1) rawDelta.setX(delta); } } if (!angleDelta.isNull()) { const int dpr = int(platformWindow->devicePixelRatio()); QPoint local(fixed1616ToReal(xiDeviceEvent->event_x)/dpr, fixed1616ToReal(xiDeviceEvent->event_y)/dpr); QPoint global(fixed1616ToReal(xiDeviceEvent->root_x)/dpr, fixed1616ToReal(xiDeviceEvent->root_y)/dpr); Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); if (modifiers & Qt::AltModifier) { std::swap(angleDelta.rx(), angleDelta.ry()); std::swap(rawDelta.rx(), rawDelta.ry()); } QWindowSystemInterface::handleWheelEvent(platformWindow, xiEvent->time, local, global, rawDelta, angleDelta, modifiers); } } } else if (xiEvent->evtype == XI_ButtonRelease && scrollingDevice.legacyOrientations) { xXIDeviceEvent* xiDeviceEvent = reinterpret_cast(event); if (QWindow *platformWindow = windowFromId(xiDeviceEvent->event)) { QPoint angleDelta; if (scrollingDevice.legacyOrientations & Qt::Vertical) { if (xiDeviceEvent->detail == 4) angleDelta.setY(120); else if (xiDeviceEvent->detail == 5) angleDelta.setY(-120); } if (scrollingDevice.legacyOrientations & Qt::Horizontal) { if (xiDeviceEvent->detail == 6) angleDelta.setX(120); else if (xiDeviceEvent->detail == 7) angleDelta.setX(-120); } if (!angleDelta.isNull()) { const int dpr = int(platformWindow->devicePixelRatio()); QPoint local(fixed1616ToReal(xiDeviceEvent->event_x)/dpr, fixed1616ToReal(xiDeviceEvent->event_y)/dpr); QPoint global(fixed1616ToReal(xiDeviceEvent->root_x)/dpr, fixed1616ToReal(xiDeviceEvent->root_y)/dpr); Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); if (modifiers & Qt::AltModifier) std::swap(angleDelta.rx(), angleDelta.ry()); QWindowSystemInterface::handleWheelEvent(platformWindow, xiEvent->time, local, global, QPoint(), angleDelta, modifiers); } } } #else Q_UNUSED(event); Q_UNUSED(scrollingDevice); #endif // XCB_USE_XINPUT21 } #endif // QT_NO_TABLETEVENT #endif // XCB_USE_XINPUT2 diff --git a/libs/ui/kis_animation_exporter.cpp b/libs/ui/kis_animation_exporter.cpp index 0dada1e4ea..65c8003055 100644 --- a/libs/ui/kis_animation_exporter.cpp +++ b/libs/ui/kis_animation_exporter.cpp @@ -1,390 +1,393 @@ /* * 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_exporter.h" #include #include #include #include #include #include "KoFileDialog.h" #include "KisDocument.h" +#include #include "kis_image.h" #include "KisImportExportManager.h" #include "kis_image_animation_interface.h" #include "KisPart.h" #include "KisMainWindow.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_time_range.h" #include "kis_painter.h" #include "kis_image_lock_hijacker.h" struct KisAnimationExporterUI::Private { QWidget *parentWidget; KisAnimationExportSaver *exporter; Private(QWidget *parent) : parentWidget(parent), exporter(0) {} }; KisAnimationExporterUI::KisAnimationExporterUI(QWidget *parent) : m_d(new Private(parent)) { } KisAnimationExporterUI::~KisAnimationExporterUI() { if (m_d->exporter) { delete m_d->exporter; } } struct KisAnimationExporter::Private { - Private(KisDocument *document, int fromTime, int toTime) - : document(document) - , image(document->image()) + Private(KisImageSP _image, int fromTime, int toTime, KoUpdaterPtr _updater) + : updater(_updater) + , image(_image) , firstFrame(fromTime) , lastFrame(toTime) , currentFrame(-1) - , batchMode(document->fileBatchMode()) + , batchMode(!updater) , isCancelled(false) , status(KisImportExportFilter::OK) , tmpDevice(new KisPaintDevice(image->colorSpace())) { + KIS_SAFE_ASSERT_RECOVER_NOOP(bool(updater) == !batchMode); } - KisDocument *document; + KoUpdaterPtr updater; KisImageWSP image; int firstFrame; int lastFrame; int currentFrame; bool batchMode; bool isCancelled; KisImportExportFilter::ConversionStatus status; SaveFrameCallback saveFrameCallback; KisPaintDeviceSP tmpDevice; KisPropertiesConfigurationSP exportConfiguration; QProgressDialog progress; }; -KisAnimationExporter::KisAnimationExporter(KisDocument *document, int fromTime, int toTime) - : m_d(new Private(document, fromTime, toTime)) +KisAnimationExporter::KisAnimationExporter(KisImageWSP image, int fromTime, int toTime, KoUpdaterPtr updater) + : m_d(new Private(image, fromTime, toTime, updater)) { connect(m_d->image->animationInterface(), SIGNAL(sigFrameReady(int)), this, SLOT(frameReadyToCopy(int)), Qt::DirectConnection); connect(this, SIGNAL(sigFrameReadyToSave()), this, SLOT(frameReadyToSave()), Qt::QueuedConnection); } KisAnimationExporter::~KisAnimationExporter() { } void KisAnimationExporter::setExportConfiguration(KisPropertiesConfigurationSP exportConfiguration) { m_d->exportConfiguration = exportConfiguration; } void KisAnimationExporter::setSaveFrameCallback(SaveFrameCallback func) { m_d->saveFrameCallback = func; } KisImportExportFilter::ConversionStatus KisAnimationExporter::exportAnimation() { if (!m_d->batchMode) { QString message = i18n("Preparing to export frames..."); m_d->progress.reset(); m_d->progress.setLabelText(message); m_d->progress.setWindowModality(Qt::ApplicationModal); m_d->progress.setCancelButton(0); m_d->progress.setMinimumDuration(0); m_d->progress.setValue(0); m_d->progress.setMinimum(0); m_d->progress.setMaximum(100); + } - emit m_d->document->statusBarMessage(message); - emit m_d->document->sigProgress(0); - connect(m_d->document, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); + if (m_d->updater) { + m_d->updater->setProgress(0); } /** * HACK ALERT: Here we remove the image lock! We do it in a GUI * thread under the barrier lock held, so it is * guaranteed no other stroke will accidentally be * started by this. And showing an app-modal dialog to * the user will prevent him from doing anything * nasty. */ KisImageLockHijacker badGuy(m_d->image); Q_UNUSED(badGuy); KIS_ASSERT_RECOVER(!m_d->image->locked()) { return KisImportExportFilter::InternalError; } m_d->status = KisImportExportFilter::OK; m_d->currentFrame = m_d->firstFrame; m_d->image->animationInterface()->requestFrameRegeneration(m_d->currentFrame, m_d->image->bounds()); QEventLoop loop; loop.connect(this, SIGNAL(sigFinished()), SLOT(quit())); loop.exec(); if (!m_d->batchMode) { - disconnect(m_d->document, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); - emit m_d->document->sigProgress(100); - emit m_d->document->clearStatusBarMessage(); m_d->progress.reset(); } + if (m_d->updater) { + m_d->updater->setProgress(100); + } + return m_d->status; } void KisAnimationExporter::cancel() { m_d->isCancelled = true; } void KisAnimationExporter::frameReadyToCopy(int time) { if (time != m_d->currentFrame) return; QRect rc = m_d->image->bounds(); KisPainter::copyAreaOptimized(rc.topLeft(), m_d->image->projection(), m_d->tmpDevice, rc); emit sigFrameReadyToSave(); } void KisAnimationExporter::frameReadyToSave() { KIS_ASSERT_RECOVER(m_d->saveFrameCallback) { m_d->status = KisImportExportFilter::InternalError; emit sigFinished(); return; } - if (m_d->isCancelled) { + // TODO: refactor to a signal! + if (m_d->isCancelled || (m_d->updater && m_d->updater->interrupted())) { m_d->status = KisImportExportFilter::UserCancelled; emit sigFinished(); return; } KisImportExportFilter::ConversionStatus result = KisImportExportFilter::OK; int time = m_d->currentFrame; result = m_d->saveFrameCallback(time, m_d->tmpDevice, m_d->exportConfiguration); - if (!m_d->batchMode) { - int length = m_d->lastFrame - m_d->firstFrame + 1; - emit m_d->document->sigProgress((time - m_d->firstFrame) * 100 / length); + if (m_d->updater) { + int length = m_d->lastFrame - m_d->firstFrame; + m_d->updater->setProgress((time - m_d->firstFrame) * 100 / length); } - qDebug() << result << time << m_d->lastFrame; - + // TODO: make translatable!! QString dialogText = QString("Exporting Frame ").append(QString::number(time)).append(" of ").append(QString::number(m_d->lastFrame)); int percentageProcessed = (float(time) / float(m_d->lastFrame) * 100); m_d->progress.setLabelText(dialogText); m_d->progress.setValue(int(percentageProcessed)); if (result == KisImportExportFilter::OK && time < m_d->lastFrame) { m_d->currentFrame = time + 1; m_d->image->animationInterface()->requestFrameRegeneration(m_d->currentFrame, m_d->image->bounds()); } else { m_d->status = result; emit sigFinished(); } } struct KisAnimationExportSaver::Private { - Private(KisDocument *document, int fromTime, int toTime, int _sequenceNumberingOffset) - : document(document) - , image(document->image()) + Private(KisDocument *_document, int fromTime, int toTime, int _sequenceNumberingOffset, KoUpdaterPtr _updater) + : document(_document) + , image(_document->image()) , firstFrame(fromTime) , lastFrame(toTime) , sequenceNumberingOffset(_sequenceNumberingOffset) + , updater(_updater) , tmpDoc(KisPart::instance()->createDocument()) - , exporter(document, fromTime, toTime) + , exporter(_document->image(), fromTime, toTime, _updater) { tmpDoc->setAutoSaveDelay(0); - tmpImage = new KisImage(tmpDoc->createUndoStore(), + KisImageSP tmpImage = new KisImage(tmpDoc->createUndoStore(), image->bounds().width(), image->bounds().height(), image->colorSpace(), QString()); tmpImage->setResolution(image->xRes(), image->yRes()); tmpDoc->setCurrentImage(tmpImage); KisPaintLayer* paintLayer = new KisPaintLayer(tmpImage, "paint device", 255); tmpImage->addNode(paintLayer, tmpImage->rootLayer(), KisLayerSP(0)); tmpDevice = paintLayer->paintDevice(); } KisDocument *document; KisImageWSP image; int firstFrame; int lastFrame; int sequenceNumberingOffset; + KoUpdaterPtr updater; QScopedPointer tmpDoc; - KisImageSP tmpImage; KisPaintDeviceSP tmpDevice; + QByteArray outputMimeType; KisAnimationExporter exporter; QString filenamePrefix; QString filenameSuffix; }; -KisAnimationExportSaver::KisAnimationExportSaver(KisDocument *document, const QString &baseFilename, int fromTime, int toTime, int sequenceNumberingOffset) - : m_d(new Private(document, fromTime, toTime, sequenceNumberingOffset)) +KisAnimationExportSaver::KisAnimationExportSaver(KisDocument *document, const QString &baseFilename, int fromTime, int toTime, int sequenceNumberingOffset, KoUpdaterPtr updater) + : m_d(new Private(document, fromTime, toTime, sequenceNumberingOffset, updater)) { int baseLength = baseFilename.lastIndexOf("."); if (baseLength > -1) { m_d->filenamePrefix = baseFilename.left(baseLength); m_d->filenameSuffix = baseFilename.right(baseFilename.length() - baseLength); } else { m_d->filenamePrefix = baseFilename; } - QString mimefilter = KisMimeDatabase::mimeTypeForFile(baseFilename); - m_d->tmpDoc->setOutputMimeType(mimefilter.toLatin1()); + m_d->outputMimeType = KisMimeDatabase::mimeTypeForFile(baseFilename).toLatin1(); m_d->tmpDoc->setFileBatchMode(true); using namespace std::placeholders; // For _1 placeholder m_d->exporter.setSaveFrameCallback(std::bind(&KisAnimationExportSaver::saveFrameCallback, this, _1, _2, _3)); - - - } KisAnimationExportSaver::~KisAnimationExportSaver() { } KisImportExportFilter::ConversionStatus KisAnimationExportSaver::exportAnimation(KisPropertiesConfigurationSP cfg) { QFileInfo info(savedFilesMaskWildcard()); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); QStringList filesList = dir.entryList({ info.fileName() }); if (!filesList.isEmpty()) { - if (m_d->document->fileBatchMode()) { + // we are in batch mode! + if (!m_d->updater) { return KisImportExportFilter::CreationError; } QStringList truncatedList = filesList; while (truncatedList.size() > 3) { truncatedList.takeLast(); } QString exampleFiles = truncatedList.join(", "); if (truncatedList.size() != filesList.size()) { exampleFiles += QString(", ..."); } QMessageBox::StandardButton result = QMessageBox::warning(0, i18n("Delete old frames?"), i18n("Frames with the same naming " "scheme exist in the destination " "directory. They are going to be " "deleted, continue?\n\n" "Directory: %1\n" "Files: %2", info.absolutePath(), exampleFiles), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (result == QMessageBox::Yes) { Q_FOREACH (const QString &file, filesList) { if (!dir.remove(file)) { QMessageBox::critical(0, i18n("Failed to delete"), i18n("Failed to delete an old frame file:\n\n" "%1\n\n" "Rendering cancelled.", dir.absoluteFilePath(file))); return KisImportExportFilter::CreationError; } } } else { return KisImportExportFilter::UserCancelled; } } m_d->exporter.setExportConfiguration(cfg); return m_d->exporter.exportAnimation(); } KisImportExportFilter::ConversionStatus KisAnimationExportSaver::saveFrameCallback(int time, KisPaintDeviceSP frame, KisPropertiesConfigurationSP exportConfiguration) { KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; QString frameNumber = QString("%1").arg(time - m_d->firstFrame + m_d->sequenceNumberingOffset, 4, 10, QChar('0')); QString filename = m_d->filenamePrefix + frameNumber + m_d->filenameSuffix; QRect rc = m_d->image->bounds(); KisPainter::copyAreaOptimized(rc.topLeft(), frame, m_d->tmpDevice, rc); - if (!m_d->tmpDoc->exportDocument(QUrl::fromLocalFile(filename), exportConfiguration)) { - status = KisImportExportFilter::CreationError; + + if (!m_d->tmpDoc->exportDocumentSync(QUrl::fromLocalFile(filename), m_d->outputMimeType, exportConfiguration)) { + status = KisImportExportFilter::InternalError; } return status; } QString KisAnimationExportSaver::savedFilesMask() const { return m_d->filenamePrefix + "%04d" + m_d->filenameSuffix; } QString KisAnimationExportSaver::savedFilesMaskWildcard() const { return m_d->filenamePrefix + "????" + m_d->filenameSuffix; } diff --git a/libs/ui/kis_animation_exporter.h b/libs/ui/kis_animation_exporter.h index f7f429c01a..9fcf8c70fc 100644 --- a/libs/ui/kis_animation_exporter.h +++ b/libs/ui/kis_animation_exporter.h @@ -1,112 +1,112 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ANIMATION_EXPORTER_H #define KIS_ANIMATION_EXPORTER_H #include "kis_types.h" #include "kritaui_export.h" #include #include class KisDocument; /** * @brief The KisAnimationExporterUI class */ class KRITAUI_EXPORT KisAnimationExporterUI : public QObject { Q_OBJECT public: KisAnimationExporterUI(QWidget *parent); ~KisAnimationExporterUI() override; private: struct Private; QScopedPointer m_d; }; /** * @brief The KisAnimationExporter class */ class KRITAUI_EXPORT KisAnimationExporter : public QObject { Q_OBJECT public: typedef std::function SaveFrameCallback; public: - KisAnimationExporter(KisDocument *document, int fromTime, int toTime); + KisAnimationExporter(KisImageWSP image, int fromTime, int toTime, KoUpdaterPtr updater); ~KisAnimationExporter() override; void setExportConfiguration(KisPropertiesConfigurationSP exportConfiguration); KisImportExportFilter::ConversionStatus exportAnimation(); void setSaveFrameCallback(SaveFrameCallback func); Q_SIGNALS: // Internal, used for getting back to main thread void sigFrameReadyToSave(); void sigFinished(); private Q_SLOTS: void frameReadyToCopy(int time); void frameReadyToSave(); void cancel(); private: struct Private; QScopedPointer m_d; }; /** * @brief The KisAnimationExportSaver class */ class KRITAUI_EXPORT KisAnimationExportSaver : public QObject { Q_OBJECT public: - KisAnimationExportSaver(KisDocument *document, const QString &baseFilename, int fromTime, int toTime, int sequenceNumberingOffset = 0); + KisAnimationExportSaver(KisDocument *document, const QString &baseFilename, int fromTime, int toTime, int sequenceNumberingOffset = 0, KoUpdaterPtr updater = 0); ~KisAnimationExportSaver() override; KisImportExportFilter::ConversionStatus exportAnimation(KisPropertiesConfigurationSP cfg = 0); /** * A standard exported files mask for ffmpeg */ QString savedFilesMask() const; /** * Wildcards are not supported ffmpeg on Windows, so they are used for QDir * only. */ QString savedFilesMaskWildcard() const; private: KisImportExportFilter::ConversionStatus saveFrameCallback(int time, KisPaintDeviceSP frame, KisPropertiesConfigurationSP exportConfiguration = 0); private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/libs/ui/kis_animation_importer.cpp b/libs/ui/kis_animation_importer.cpp index dad3afda2f..ba3db9c7c4 100644 --- a/libs/ui/kis_animation_importer.cpp +++ b/libs/ui/kis_animation_importer.cpp @@ -1,129 +1,130 @@ /* * 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_importer.h" #include #include "KoColorSpace.h" +#include +#include #include "KisPart.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_undo_adapter.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_raster_keyframe_channel.h" #include "commands/kis_image_layer_add_command.h" struct KisAnimationImporter::Private { KisImageSP image; KisDocument *document; bool stop; + KoUpdaterPtr updater; }; -KisAnimationImporter::KisAnimationImporter(KisImageSP image) +KisAnimationImporter::KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater) : m_d(new Private()) { - m_d->document= 0; + m_d->document = 0; m_d->image = image; m_d->stop = false; + m_d->updater = updater; } KisAnimationImporter::KisAnimationImporter(KisDocument* document) : m_d(new Private()) { m_d->document= document; m_d->image = document->image(); m_d->stop = false; } KisAnimationImporter::~KisAnimationImporter() {} KisImportExportFilter::ConversionStatus KisAnimationImporter::import(QStringList files, int firstFrame, int step) { Q_ASSERT(step > 0); - bool batchMode; - - if (m_d->document) { - batchMode= m_d->document->fileBatchMode(); - - if (!batchMode) { - emit m_d->document->statusBarMessage(i18n("Import frames")); - emit m_d->document->sigProgress(0); - connect(m_d->document, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); - } - } else batchMode = false; - m_d->image->lock(); KisUndoAdapter *undo = m_d->image->undoAdapter(); undo->beginMacro(kundo2_i18n("Import animation")); QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setFileBatchMode(true); KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; int frame = firstFrame; + int filesProcessed = 0; - KisRasterKeyframeChannel *contentChannel = 0; + if (m_d->updater) { + m_d->updater->setRange(0, files.size() - 1); + } + KisRasterKeyframeChannel *contentChannel = 0; Q_FOREACH(QString file, files) { - bool successfullyLoaded = importDoc->openUrl(QUrl::fromLocalFile(file), KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES); + bool successfullyLoaded = importDoc->openUrl(QUrl::fromLocalFile(file), KisDocument::DontAddToRecent); if (!successfullyLoaded) { status = KisImportExportFilter::InternalError; break; } - if (m_d->stop) { - status = KisImportExportFilter::ProgressCancelled; - break; - } - if (frame == firstFrame) { const KoColorSpace *cs = importDoc->image()->colorSpace(); KisPaintLayerSP paintLayer = new KisPaintLayer(m_d->image, m_d->image->nextLayerName(), OPACITY_OPAQUE_U8, cs); undo->addCommand(new KisImageLayerAddCommand(m_d->image, paintLayer, m_d->image->rootLayer(), m_d->image->rootLayer()->childCount())); paintLayer->enableAnimation(); contentChannel = qobject_cast(paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } - if (!batchMode) { - emit m_d->document->sigProgress((frame - firstFrame) * 100 / (step * files.size())); + if (m_d->updater) { + if (m_d->updater->interrupted()) { + m_d->stop = true; + } else { + m_d->updater->setValue(filesProcessed); + + // the updater doesn't call that automatically, + // it is "threaded" by default + qApp->processEvents(); + } + } + + if (m_d->stop) { + status = KisImportExportFilter::ProgressCancelled; + break; } + contentChannel->importFrame(frame, importDoc->image()->projection(), NULL); frame += step; + filesProcessed++; } - if (!batchMode) { - disconnect(m_d->document, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); - emit m_d->document->sigProgress(100); - emit m_d->document->clearStatusBarMessage(); - } undo->endMacro(); m_d->image->unlock(); return status; } void KisAnimationImporter::cancel() { m_d->stop = true; } diff --git a/libs/ui/kis_animation_importer.h b/libs/ui/kis_animation_importer.h index b5d2761371..565db0ecd7 100644 --- a/libs/ui/kis_animation_importer.h +++ b/libs/ui/kis_animation_importer.h @@ -1,48 +1,48 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ANIMATION_IMPORTER_H #define KIS_ANIMATION_IMPORTER_H #include "kis_types.h" #include "kritaui_export.h" #include class KisDocument; class KisMainWindow; class KRITAUI_EXPORT KisAnimationImporter : public QObject { Q_OBJECT public: - KisAnimationImporter(KisImageSP image); + KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater = 0); KisAnimationImporter(KisDocument* document); ~KisAnimationImporter() override; KisImportExportFilter::ConversionStatus import(QStringList files, int firstFrame, int step); private Q_SLOTS: void cancel(); private: struct Private; QScopedPointer m_d; }; #endif diff --git a/libs/ui/kis_async_action_feedback.cpp b/libs/ui/kis_async_action_feedback.cpp index 987a8de7c4..255e758d48 100644 --- a/libs/ui/kis_async_action_feedback.cpp +++ b/libs/ui/kis_async_action_feedback.cpp @@ -1,62 +1,87 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_async_action_feedback.h" #include #include struct KisAsyncActionFeedback::Private { QScopedPointer progress; }; KisAsyncActionFeedback::KisAsyncActionFeedback(const QString &message, QWidget *parent) : m_d(new Private) { m_d->progress.reset(new QProgressDialog(message, "", 0, 0, parent)); m_d->progress->setWindowModality(Qt::ApplicationModal); m_d->progress->setCancelButton(0); m_d->progress->setMinimumDuration(1000); m_d->progress->setValue(0); + + // disable close button + m_d->progress->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); } KisAsyncActionFeedback::~KisAsyncActionFeedback() { } template T runActionImpl(std::function func) { QFuture result = QtConcurrent::run(func); QFutureWatcher watcher; watcher.setFuture(result); while (watcher.isRunning()) { qApp->processEvents(); } watcher.waitForFinished(); return watcher.result(); } KisImportExportFilter::ConversionStatus KisAsyncActionFeedback::runAction(std::function func) { return runActionImpl(func); } + +void KisAsyncActionFeedback::runVoidAction(std::function func) +{ + QFuture result = QtConcurrent::run(func); + QFutureWatcher watcher; + watcher.setFuture(result); + + while (watcher.isRunning()) { + qApp->processEvents(); + } + + watcher.waitForFinished(); +} + +void KisAsyncActionFeedback::waitForMutex(QMutex *mutex) +{ + while (!mutex->tryLock()) { + qApp->processEvents(); + } + + mutex->unlock(); +} diff --git a/libs/ui/kis_async_action_feedback.h b/libs/ui/kis_async_action_feedback.h index 40b242d4d2..a615a45585 100644 --- a/libs/ui/kis_async_action_feedback.h +++ b/libs/ui/kis_async_action_feedback.h @@ -1,41 +1,44 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_ASYNC_ACTION_FEEDBACK_H #define __KIS_ASYNC_ACTION_FEEDBACK_H #include #include #include "KisImportExportFilter.h" class QWidget; +class QMutex; class KisAsyncActionFeedback { public: KisAsyncActionFeedback(const QString &message, QWidget *parent); ~KisAsyncActionFeedback(); KisImportExportFilter::ConversionStatus runAction(std::function func); + void runVoidAction(std::function func); + void waitForMutex(QMutex *mutex); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_ASYNC_ACTION_FEEDBACK_H */ diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index fbbb1e9d5e..bb5f4d8017 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,1876 +1,1887 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include KisConfig::KisConfig() : m_cfg( KSharedConfig::openConfig()->group("")) { } KisConfig::~KisConfig() { if (qApp->thread() != QThread::currentThread()) { //dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Skipping..."; return; } m_cfg.sync(); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } QColor KisConfig::getCursorMainColor(bool defaultValue) const { QColor col; col.setRgbF(0.501961, 1.0, 0.501961); - return (defaultValue ? col : m_cfg.readEntry("cursormaincolor", col)); + return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col)); } void KisConfig::setCursorMainColor(const QColor &v) const { - m_cfg.writeEntry("cursormaincolor", v); + m_cfg.writeEntry("cursorMaincColor", v); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QColor KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); return (defaultValue ? col : m_cfg.readEntry("mdiBackgroundColor", col)); } void KisConfig::setMDIBackgroundColor(const QColor &v) const { m_cfg.writeEntry("mdiBackgroundColor", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg; QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); if (bytes.length() > 0) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); return profile; } else { //dbgKrita << "\tCould not get a system monitor profile"; return 0; } } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } //dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS"); QString cs = canvasState(); return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); } void KisConfig::setUseOpenGL(bool useOpenGL) const { m_cfg.writeEntry("useOpenGL", useOpenGL); } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } qint32 KisConfig::maxNumberOfThreads(bool defaultValue) const { return (defaultValue ? QThread::idealThreadCount() : m_cfg.readEntry("maxthreads", QThread::idealThreadCount())); } void KisConfig::setMaxNumberOfThreads(qint32 maxThreads) { m_cfg.writeEntry("maxthreads", maxThreads); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } -QColor KisConfig::getOpenGLGridColor(bool defaultValue) const +QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); - return (defaultValue ? col : m_cfg.readEntry("openglgridcolor", col)); + return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } -void KisConfig::setOpenGLGridColor(const QColor & v) const +void KisConfig::setPixelGridColor(const QColor & v) const { - m_cfg.writeEntry("openglgridcolor", v); + m_cfg.writeEntry("pixelGridColor", v); } -qreal KisConfig::getOpenGLGridDrawingThreshold(bool defaultValue) const +qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 8.0f; - return (defaultValue ? border : m_cfg.readEntry("griddrawingborder", border)); + return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } -void KisConfig::setOpenGLGridDrawingThreshold(qreal v) const +void KisConfig::setPixelGridDrawingThreshold(qreal v) const { - m_cfg.writeEntry("griddrawingborder", v); + m_cfg.writeEntry("pixelGridDrawingThreshold", v); +} + +bool KisConfig::pixelGridEnabled(bool defaultValue) const +{ + bool enabled = true; + return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); +} + +void KisConfig::enablePixelGrid(bool v) const +{ + m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); } qint32 KisConfig::checkSize(bool defaultValue) const { return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); } void KisConfig::setCheckSize(qint32 checksize) const { m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } QColor KisConfig::selectionOverlayMaskColor(bool defaultValue) const { QColor def(255, 0, 0, 220); return (defaultValue ? def : m_cfg.readEntry("selectionOverlayMaskColor", def)); } void KisConfig::setSelectionOverlayMaskColor(const QColor &color) { m_cfg.writeEntry("selectionOverlayMaskColor", color); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::hideSplashScreen(bool defaultValue) const { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); return (defaultValue ? true : cfg.readEntry("HideSplashAfterStartup", true)); } void KisConfig::setHideSplashScreen(bool hideSplashScreen) const { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); cfg.writeEntry("HideSplashAfterStartup", hideSplashScreen); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("presetIconSize", 30)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockerTitleBars(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockerTitleBars", true)); } void KisConfig::setShowDockerTitleBars(const bool value) const { m_cfg.writeEntry("showDockerTitleBars", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList())); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } QString KisConfig::ocioConfigurationPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString())); } void KisConfig::setOcioConfigurationPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", path); } QString KisConfig::ocioLutPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString())); } void KisConfig::setOcioLutPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioLutPath", path); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", QString())); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(QColor value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::paletteDockerPaletteViewSectionSize(bool defaultValue) const { return (defaultValue ? 12 : m_cfg.readEntry("paletteDockerPaletteViewSectionSize", 12)); } void KisConfig::setPaletteDockerPaletteViewSectionSize(int value) const { m_cfg.writeEntry("paletteDockerPaletteViewSectionSize", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } bool KisConfig::useVerboseOpenGLDebugOutput(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useVerboseOpenGLDebugOutput", false)); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLDebugging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLDebugging", false)); } void KisConfig::setEnableOpenGLDebugging(bool value) const { m_cfg.writeEntry("enableOpenGLDebugging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const { const bool defaultEnabled = true; return defaultValue ? defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled); } void KisConfig::setStabilizerDelayedPaint(bool value) { m_cfg.writeEntry("stabilizerDelayedPaint", value); } QString KisConfig::customFFMpegPath(bool defaultValue) const { return defaultValue ? QString() : m_cfg.readEntry("ffmpegExecutablePath", QString()); } void KisConfig::setCustomFFMpegPath(const QString &value) const { m_cfg.writeEntry("ffmpegExecutablePath", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true); } void KisConfig::setCalculateAnimationCacheInBackground(bool value) { m_cfg.writeEntry("calculateAnimationCacheInBackground", value); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const { QDomDocument doc; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } return color; } diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h index 717d1432aa..0ea2b74a30 100644 --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -1,561 +1,564 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CONFIG_H_ #define KIS_CONFIG_H_ #include #include #include #include #include #include #include "kis_global.h" #include "kis_properties_configuration.h" #include "kritaui_export.h" class KoColorProfile; class KoColorSpace; class KisSnapConfig; class KRITAUI_EXPORT KisConfig { public: KisConfig(); ~KisConfig(); bool disableTouchOnCanvas(bool defaultValue = false) const; void setDisableTouchOnCanvas(bool value) const; bool useProjections(bool defaultValue = false) const; void setUseProjections(bool useProj) const; bool undoEnabled(bool defaultValue = false) const; void setUndoEnabled(bool undo) const; int undoStackLimit(bool defaultValue = false) const; void setUndoStackLimit(int limit) const; bool useCumulativeUndoRedo(bool defaultValue = false) const; void setCumulativeUndoRedo(bool value); double stackT1(bool defaultValue = false) const; void setStackT1(int T1); double stackT2(bool defaultValue = false) const; void setStackT2(int T2); int stackN(bool defaultValue = false) const; void setStackN(int N); qint32 defImageWidth(bool defaultValue = false) const; void defImageWidth(qint32 width) const; qint32 defImageHeight(bool defaultValue = false) const; void defImageHeight(qint32 height) const; qreal defImageResolution(bool defaultValue = false) const; void defImageResolution(qreal res) const; /** * @return the id of the default color model used for creating new images. */ QString defColorModel(bool defaultValue = false) const; /** * set the id of the default color model used for creating new images. */ void defColorModel(const QString & model) const; /** * @return the id of the default color depth used for creating new images. */ QString defaultColorDepth(bool defaultValue = false) const; /** * set the id of the default color depth used for creating new images. */ void setDefaultColorDepth(const QString & depth) const; /** * @return the id of the default color profile used for creating new images. */ QString defColorProfile(bool defaultValue = false) const; /** * set the id of the default color profile used for creating new images. */ void defColorProfile(const QString & depth) const; CursorStyle newCursorStyle(bool defaultValue = false) const; void setNewCursorStyle(CursorStyle style); QColor getCursorMainColor(bool defaultValue = false) const; void setCursorMainColor(const QColor& v) const; OutlineStyle newOutlineStyle(bool defaultValue = false) const; void setNewOutlineStyle(OutlineStyle style); QRect colorPreviewRect() const; void setColorPreviewRect(const QRect &rect); /// get the profile the user has selected for the given screen QString monitorProfile(int screen) const; void setMonitorProfile(int screen, const QString & monitorProfile, bool override) const; QString monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue = true) const; void setMonitorForScreen(int screen, const QString& monitor); /// Get the actual profile to be used for the given screen, which is /// either the screen profile set by the color management system or /// the custom monitor profile set by the user, depending on the configuration const KoColorProfile *displayProfile(int screen) const; QString workingColorSpace(bool defaultValue = false) const; void setWorkingColorSpace(const QString & workingColorSpace) const; QString importProfile(bool defaultValue = false) const; void setImportProfile(const QString & importProfile) const; QString printerColorSpace(bool defaultValue = false) const; void setPrinterColorSpace(const QString & printerColorSpace) const; QString printerProfile(bool defaultValue = false) const; void setPrinterProfile(const QString & printerProfile) const; bool useBlackPointCompensation(bool defaultValue = false) const; void setUseBlackPointCompensation(bool useBlackPointCompensation) const; bool allowLCMSOptimization(bool defaultValue = false) const; void setAllowLCMSOptimization(bool allowLCMSOptimization); void writeKoColor(const QString& name, const KoColor& color) const; KoColor readKoColor(const QString& name, const KoColor& color = KoColor()) const; bool showRulers(bool defaultValue = false) const; void setShowRulers(bool rulers) const; bool rulersTrackMouse(bool defaultValue = false) const; void setRulersTrackMouse(bool value) const; qint32 pasteBehaviour(bool defaultValue = false) const; void setPasteBehaviour(qint32 behaviour) const; qint32 monitorRenderIntent(bool defaultValue = false) const; void setRenderIntent(qint32 monitorRenderIntent) const; bool useOpenGL(bool defaultValue = false) const; void setUseOpenGL(bool useOpenGL) const; int openGLFilteringMode(bool defaultValue = false) const; void setOpenGLFilteringMode(int filteringMode); bool useOpenGLTextureBuffer(bool defaultValue = false) const; void setUseOpenGLTextureBuffer(bool useBuffer); bool disableVSync(bool defaultValue = false) const; void setDisableVSync(bool disableVSync); bool showAdvancedOpenGLSettings(bool defaultValue = false) const; bool forceOpenGLFenceWorkaround(bool defaultValue = false) const; int numMipmapLevels(bool defaultValue = false) const; int openGLTextureSize(bool defaultValue = false) const; int textureOverlapBorder() const; qint32 maxNumberOfThreads(bool defaultValue = false) const; void setMaxNumberOfThreads(qint32 numberOfThreads); quint32 getGridMainStyle(bool defaultValue = false) const; void setGridMainStyle(quint32 v) const; quint32 getGridSubdivisionStyle(bool defaultValue = false) const; void setGridSubdivisionStyle(quint32 v) const; QColor getGridMainColor(bool defaultValue = false) const; void setGridMainColor(const QColor & v) const; QColor getGridSubdivisionColor(bool defaultValue = false) const; void setGridSubdivisionColor(const QColor & v) const; - QColor getOpenGLGridColor(bool defaultValue = false) const; - void setOpenGLGridColor(const QColor & v) const; + QColor getPixelGridColor(bool defaultValue = false) const; + void setPixelGridColor(const QColor & v) const; - qreal getOpenGLGridDrawingThreshold(bool defaultValue = false) const; - void setOpenGLGridDrawingThreshold(qreal v) const; + qreal getPixelGridDrawingThreshold(bool defaultValue = false) const; + void setPixelGridDrawingThreshold(qreal v) const; + + bool pixelGridEnabled(bool defaultValue = false) const; + void enablePixelGrid(bool v) const; quint32 guidesLineStyle(bool defaultValue = false) const; void setGuidesLineStyle(quint32 v) const; QColor guidesColor(bool defaultValue = false) const; void setGuidesColor(const QColor & v) const; void loadSnapConfig(KisSnapConfig *config, bool defaultValue = false) const; void saveSnapConfig(const KisSnapConfig &config); qint32 checkSize(bool defaultValue = false) const; void setCheckSize(qint32 checkSize) const; bool scrollCheckers(bool defaultValue = false) const; void setScrollingCheckers(bool scollCheckers) const; QColor checkersColor1(bool defaultValue = false) const; void setCheckersColor1(const QColor & v) const; QColor checkersColor2(bool defaultValue = false) const; void setCheckersColor2(const QColor & v) const; QColor canvasBorderColor(bool defaultValue = false) const; void setCanvasBorderColor(const QColor &color) const; bool hideScrollbars(bool defaultValue = false) const; void setHideScrollbars(bool value) const; bool antialiasCurves(bool defaultValue = false) const; void setAntialiasCurves(bool v) const; QColor selectionOverlayMaskColor(bool defaultValue = false) const; void setSelectionOverlayMaskColor(const QColor &color); bool antialiasSelectionOutline(bool defaultValue = false) const; void setAntialiasSelectionOutline(bool v) const; bool showRootLayer(bool defaultValue = false) const; void setShowRootLayer(bool showRootLayer) const; bool showGlobalSelection(bool defaultValue = false) const; void setShowGlobalSelection(bool showGlobalSelection) const; bool showOutlineWhilePainting(bool defaultValue = false) const; void setShowOutlineWhilePainting(bool showOutlineWhilePainting) const; bool hideSplashScreen(bool defaultValue = false) const; void setHideSplashScreen(bool hideSplashScreen) const; qreal outlineSizeMinimum(bool defaultValue = false) const; void setOutlineSizeMinimum(qreal outlineSizeMinimum) const; qreal selectionViewSizeMinimum(bool defaultValue = false) const; void setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const; int autoSaveInterval(bool defaultValue = false) const; void setAutoSaveInterval(int seconds) const; bool backupFile(bool defaultValue = false) const; void setBackupFile(bool backupFile) const; bool showFilterGallery(bool defaultValue = false) const; void setShowFilterGallery(bool showFilterGallery) const; bool showFilterGalleryLayerMaskDialog(bool defaultValue = false) const; void setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const; // OPENGL_SUCCESS, TRY_OPENGL, OPENGL_NOT_TRIED, OPENGL_FAILED QString canvasState(bool defaultValue = false) const; void setCanvasState(const QString& state) const; bool toolOptionsPopupDetached(bool defaultValue = false) const; void setToolOptionsPopupDetached(bool detached) const; bool paintopPopupDetached(bool defaultValue = false) const; void setPaintopPopupDetached(bool detached) const; QString pressureTabletCurve(bool defaultValue = false) const; void setPressureTabletCurve(const QString& curveString) const; qreal vastScrolling(bool defaultValue = false) const; void setVastScrolling(const qreal factor) const; int presetChooserViewMode(bool defaultValue = false) const; void setPresetChooserViewMode(const int mode) const; int presetIconSize(bool defaultValue = false) const; void setPresetIconSize(const int value) const; bool firstRun(bool defaultValue = false) const; void setFirstRun(const bool firstRun) const; bool clicklessSpacePan(bool defaultValue = false) const; void setClicklessSpacePan(const bool toggle) const; int horizontalSplitLines(bool defaultValue = false) const; void setHorizontalSplitLines(const int numberLines) const; int verticalSplitLines(bool defaultValue = false) const; void setVerticalSplitLines(const int numberLines) const; bool hideDockersFullscreen(bool defaultValue = false) const; void setHideDockersFullscreen(const bool value) const; bool showDockerTitleBars(bool defaultValue = false) const; void setShowDockerTitleBars(const bool value) const; bool showDockers(bool defaultValue = false) const; void setShowDockers(const bool value) const; bool showStatusBar(bool defaultValue = false) const; void setShowStatusBar(const bool value) const; bool hideMenuFullscreen(bool defaultValue = false) const; void setHideMenuFullscreen(const bool value) const; bool hideScrollbarsFullscreen(bool defaultValue = false) const; void setHideScrollbarsFullscreen(const bool value) const; bool hideStatusbarFullscreen(bool defaultValue = false) const; void setHideStatusbarFullscreen(const bool value) const; bool hideTitlebarFullscreen(bool defaultValue = false) const; void setHideTitlebarFullscreen(const bool value) const; bool hideToolbarFullscreen(bool defaultValue = false) const; void setHideToolbarFullscreen(const bool value) const; bool fullscreenMode(bool defaultValue = false) const; void setFullscreenMode(const bool value) const; QStringList favoriteCompositeOps(bool defaultValue = false) const; void setFavoriteCompositeOps(const QStringList& compositeOps) const; QString exportConfiguration(const QString &filterId, bool defaultValue = false) const; void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; QString importConfiguration(const QString &filterId, bool defaultValue = false) const; void setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; bool useOcio(bool defaultValue = false) const; void setUseOcio(bool useOCIO) const; int favoritePresets(bool defaultValue = false) const; void setFavoritePresets(const int value); bool levelOfDetailEnabled(bool defaultValue = false) const; void setLevelOfDetailEnabled(bool value); enum OcioColorManagementMode { INTERNAL = 0, OCIO_CONFIG, OCIO_ENVIRONMENT }; OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const; void setOcioColorManagementMode(OcioColorManagementMode mode) const; QString ocioConfigurationPath(bool defaultValue = false) const; void setOcioConfigurationPath(const QString &path) const; QString ocioLutPath(bool defaultValue = false) const; void setOcioLutPath(const QString &path) const; int ocioLutEdgeSize(bool defaultValue = false) const; void setOcioLutEdgeSize(int value); bool ocioLockColorVisualRepresentation(bool defaultValue = false) const; void setOcioLockColorVisualRepresentation(bool value); bool useSystemMonitorProfile(bool defaultValue = false) const; void setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const; QString defaultPalette(bool defaultValue = false) const; void setDefaultPalette(const QString& name) const; QString toolbarSlider(int sliderNumber, bool defaultValue = false) const; void setToolbarSlider(int sliderNumber, const QString &slider); bool sliderLabels(bool defaultValue = false) const; void setSliderLabels(bool enabled); QString currentInputProfile(bool defaultValue = false) const; void setCurrentInputProfile(const QString& name); bool presetStripVisible(bool defaultValue = false) const; void setPresetStripVisible(bool visible); bool scratchpadVisible(bool defaultValue = false) const; void setScratchpadVisible(bool visible); bool showSingleChannelAsColor(bool defaultValue = false) const; void setShowSingleChannelAsColor(bool asColor); bool hidePopups(bool defaultValue = false) const; void setHidePopups(bool hidepopups); int numDefaultLayers(bool defaultValue = false) const; void setNumDefaultLayers(int num); quint8 defaultBackgroundOpacity(bool defaultValue = false) const; void setDefaultBackgroundOpacity(quint8 value); QColor defaultBackgroundColor(bool defaultValue = false) const; void setDefaultBackgroundColor(QColor value); enum BackgroundStyle { LAYER = 0, PROJECTION = 1 }; BackgroundStyle defaultBackgroundStyle(bool defaultValue = false) const; void setDefaultBackgroundStyle(BackgroundStyle value); int lineSmoothingType(bool defaultValue = false) const; void setLineSmoothingType(int value); qreal lineSmoothingDistance(bool defaultValue = false) const; void setLineSmoothingDistance(qreal value); qreal lineSmoothingTailAggressiveness(bool defaultValue = false) const; void setLineSmoothingTailAggressiveness(qreal value); bool lineSmoothingSmoothPressure(bool defaultValue = false) const; void setLineSmoothingSmoothPressure(bool value); bool lineSmoothingScalableDistance(bool defaultValue = false) const; void setLineSmoothingScalableDistance(bool value); qreal lineSmoothingDelayDistance(bool defaultValue = false) const; void setLineSmoothingDelayDistance(qreal value); bool lineSmoothingUseDelayDistance(bool defaultValue = false) const; void setLineSmoothingUseDelayDistance(bool value); bool lineSmoothingFinishStabilizedCurve(bool defaultValue = false) const; void setLineSmoothingFinishStabilizedCurve(bool value); bool lineSmoothingStabilizeSensors(bool defaultValue = false) const; void setLineSmoothingStabilizeSensors(bool value); int paletteDockerPaletteViewSectionSize(bool defaultValue = false) const; void setPaletteDockerPaletteViewSectionSize(int value) const; int tabletEventsDelay(bool defaultValue = false) const; void setTabletEventsDelay(int value); bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const; void setTestingAcceptCompressedTabletEvents(bool value); bool shouldEatDriverShortcuts(bool defaultValue = false) const; bool testingCompressBrushEvents(bool defaultValue = false) const; void setTestingCompressBrushEvents(bool value); const KoColorSpace* customColorSelectorColorSpace(bool defaultValue = false) const; void setCustomColorSelectorColorSpace(const KoColorSpace *cs); bool useDirtyPresets(bool defaultValue = false) const; void setUseDirtyPresets(bool value); bool useEraserBrushSize(bool defaultValue = false) const; void setUseEraserBrushSize(bool value); bool useEraserBrushOpacity(bool defaultValue = false) const; void setUseEraserBrushOpacity(bool value); QColor getMDIBackgroundColor(bool defaultValue = false) const; void setMDIBackgroundColor(const QColor & v) const; QString getMDIBackgroundImage(bool defaultValue = false) const; void setMDIBackgroundImage(const QString & fileName) const; bool useVerboseOpenGLDebugOutput(bool defaultValue = false) const; int workaroundX11SmoothPressureSteps(bool defaultValue = false) const; bool showCanvasMessages(bool defaultValue = false) const; void setShowCanvasMessages(bool show); bool compressKra(bool defaultValue = false) const; void setCompressKra(bool compress); bool toolOptionsInDocker(bool defaultValue = false) const; void setToolOptionsInDocker(bool inDocker); void setEnableOpenGLDebugging(bool value) const; bool enableOpenGLDebugging(bool defaultValue = false) const; void setEnableAmdVectorizationWorkaround(bool value); bool enableAmdVectorizationWorkaround(bool defaultValue = false) const; bool animationDropFrames(bool defaultValue = false) const; void setAnimationDropFrames(bool value); int scrubbingUpdatesDelay(bool defaultValue = false) const; void setScrubbingUpdatesDelay(int value); int scrubbingAudioUpdatesDelay(bool defaultValue = false) const; void setScrubbingAudioUpdatesDelay(int value); int audioOffsetTolerance(bool defaultValue = false) const; void setAudioOffsetTolerance(int value); bool switchSelectionCtrlAlt(bool defaultValue = false) const; void setSwitchSelectionCtrlAlt(bool value); bool convertToImageColorspaceOnImport(bool defaultValue = false) const; void setConvertToImageColorspaceOnImport(bool value); int stabilizerSampleSize(bool defaultValue = false) const; void setStabilizerSampleSize(int value); bool stabilizerDelayedPaint(bool defaultValue = false) const; void setStabilizerDelayedPaint(bool value); QString customFFMpegPath(bool defaultValue = false) const; void setCustomFFMpegPath(const QString &value) const; bool showBrushHud(bool defaultValue = false) const; void setShowBrushHud(bool value); QString brushHudSetting(bool defaultValue = false) const; void setBrushHudSetting(const QString &value) const; bool calculateAnimationCacheInBackground(bool defaultValue = false) const; void setCalculateAnimationCacheInBackground(bool value); template void writeEntry(const QString& name, const T& value) { m_cfg.writeEntry(name, value); } template void writeList(const QString& name, const QList& value) { m_cfg.writeEntry(name, value); } template T readEntry(const QString& name, const T& defaultValue=T()) { return m_cfg.readEntry(name, defaultValue); } template QList readList(const QString& name, const QList& defaultValue=QList()) { return m_cfg.readEntry(name, defaultValue); } /// get the profile the color managment system has stored for the given screen static const KoColorProfile* getScreenProfile(int screen); private: KisConfig(const KisConfig&); KisConfig& operator=(const KisConfig&) const; private: mutable KConfigGroup m_cfg; }; #endif // KIS_CONFIG_H_ diff --git a/libs/ui/kis_import_catcher.cc b/libs/ui/kis_import_catcher.cc index b1a2962f59..e529f44ab8 100644 --- a/libs/ui/kis_import_catcher.cc +++ b/libs/ui/kis_import_catcher.cc @@ -1,143 +1,141 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_import_catcher.h" #include #include #include #include #include #include "kis_node_manager.h" #include "kis_count_visitor.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_node_commands_adapter.h" #include "kis_group_layer.h" #include "kis_statusbar.h" #include "kis_progress_widget.h" #include "kis_config.h" #include "KisPart.h" struct KisImportCatcher::Private { public: KisDocument* doc; KisViewManager* view; QUrl url; QString layerType; QString prettyLayerName() const; void importAsPaintLayer(KisPaintDeviceSP device); void importAsTransparencyMask(KisPaintDeviceSP device); }; QString KisImportCatcher::Private::prettyLayerName() const { QString name = url.fileName(); return !name.isEmpty() ? name : url.toDisplayString(); } void KisImportCatcher::Private::importAsPaintLayer(KisPaintDeviceSP device) { KisLayerSP newLayer = new KisPaintLayer(view->image(), prettyLayerName(), OPACITY_OPAQUE_U8, device); KisNodeSP parent = 0; KisLayerSP currentActiveLayer = view->activeLayer(); if (currentActiveLayer) { parent = currentActiveLayer->parent(); } if (parent.isNull()) { parent = view->image()->rootLayer(); } KisNodeCommandsAdapter adapter(view); adapter.addNode(newLayer, parent, currentActiveLayer); } KisImportCatcher::KisImportCatcher(const QUrl &url, KisViewManager *view, const QString &layerType) : m_d(new Private) { m_d->doc = KisPart::instance()->createDocument(); - KoProgressProxy *progressProxy = view->statusBar()->progress()->progressProxy(); - m_d->doc->setProgressProxy(progressProxy); m_d->view = view; m_d->url = url; m_d->layerType = layerType; connect(m_d->doc, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished())); bool result = m_d->doc->openUrl(url); if (!result) { deleteMyself(); } } void KisImportCatcher::slotLoadingFinished() { KisImageWSP importedImage = m_d->doc->image(); importedImage->waitForDone(); if (importedImage && importedImage->projection()->exactBounds().isValid()) { if (m_d->layerType != "KisPaintLayer") { m_d->view->nodeManager()->createNode(m_d->layerType, false, importedImage->projection()); } else { KisPaintDeviceSP dev = importedImage->projection(); adaptClipToImageColorSpace(dev, m_d->view->image()); m_d->importAsPaintLayer(dev); } } deleteMyself(); } void KisImportCatcher::deleteMyself() { m_d->doc->deleteLater(); deleteLater(); } KisImportCatcher::~KisImportCatcher() { delete m_d; } void KisImportCatcher::adaptClipToImageColorSpace(KisPaintDeviceSP dev, KisImageSP image) { KisConfig cfg; qDebug() << "dev" << dev->colorSpace() << "image" << image->colorSpace() << "cfg" << cfg.convertToImageColorspaceOnImport(); if (cfg.convertToImageColorspaceOnImport() && *dev->colorSpace() != *image->colorSpace()) { /// XXX: do we need intent here? KUndo2Command* cmd = dev->convertTo(image->colorSpace()); delete cmd; } } diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp index 87c7dbed64..8954771e29 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1419 +1,1418 @@ /* * 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); //if (!shape) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); //if (!shapeLayer) return false; KIS_SAFE_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_lock"); + connect(action, SIGNAL(triggered()), this, SLOT(toggleLock())); action = actionManager->createAction("toggle_layer_inherit_alpha"); connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha())); + action = actionManager->createAction("toggle_layer_alpha_lock"); + connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock())); + 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); + doc->exportDocumentSync(url, mimefilter.toLatin1()); } 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_paintop_box.cc b/libs/ui/kis_paintop_box.cc index d97b0f4353..799baf0846 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1346 +1,1290 @@ /* * kis_paintop_box.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com) * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paintop_box.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "widgets/kis_popup_button.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_popup.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_chooser_popup.h" #include "widgets/kis_workspace_chooser.h" #include "widgets/kis_paintop_list_widget.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_widget_chooser.h" #include "tool/kis_tool.h" #include "kis_signals_blocker.h" #include "kis_action_manager.h" #include "kis_highlighted_button.h" typedef KoResourceServerSimpleConstruction > KisPaintOpPresetResourceServer; typedef KoResourceServerAdapter > KisPaintOpPresetResourceServerAdapter; KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->resourceProvider()) , m_optionWidget(0) , m_toolOptionsPopupButton(0) , m_brushEditorPopupButton(0) , m_presetSelectorPopupButton(0) , m_toolOptionsPopup(0) , m_viewManager(view) , m_previousNode(0) , m_currTabletToolID(KoInputDevice::invalid()) , m_presetsEnabled(true) , m_blockUpdate(false) , m_dirtyPresetsEnabled(false) , m_eraserBrushSizeEnabled(false) , m_eraserBrushOpacityEnabled(false) { Q_ASSERT(view != 0); setObjectName(name); KisConfig cfg; m_dirtyPresetsEnabled = cfg.useDirtyPresets(); m_eraserBrushSizeEnabled = cfg.useEraserBrushSize(); m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity(); KAcceleratorManager::setNoAccel(this); setWindowTitle(i18n("Painter's Toolchest")); + m_favoriteResourceManager = new KisFavoriteResourceManager(this); + + KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff"); int iconsize = grp.readEntry("IconSize", 32); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton = new KisPopupButton(this); m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings")); m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize); } m_brushEditorPopupButton = new KisPopupButton(this); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings")); m_brushEditorPopupButton->setFixedSize(iconsize, iconsize); m_presetSelectorPopupButton = new KisPopupButton(this); m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset")); m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize); m_eraseModeButton = new KisHighlightedToolButton(this); m_eraseModeButton->setFixedSize(iconsize, iconsize); m_eraseModeButton->setCheckable(true); m_eraseAction = m_viewManager->actionManager()->createAction("erase_action"); m_eraseModeButton->setDefaultAction(m_eraseAction); m_reloadButton = new QToolButton(this); m_reloadButton->setFixedSize(iconsize, iconsize); m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action"); m_reloadButton->setDefaultAction(m_reloadAction); m_alphaLockButton = new KisHighlightedToolButton(this); m_alphaLockButton->setFixedSize(iconsize, iconsize); m_alphaLockButton->setCheckable(true); KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha"); m_alphaLockButton->setDefaultAction(alphaLockAction); // pen pressure m_disablePressureButton = new KisHighlightedToolButton(this); m_disablePressureButton->setFixedSize(iconsize, iconsize); m_disablePressureButton->setCheckable(true); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); m_disablePressureButton->setDefaultAction(m_disablePressureAction); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); 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_savePresetWidget = new KisPresetSaveWidget(this); + + m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); + connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); m_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 oldPreset = curPreset->clone(); - oldPreset->load(); - KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); - QString saveLocation = rServer->saveLocation(); - QString presetName = m_presetsPopup->getPresetName(); - QString currentPresetFileName = saveLocation + presetName + curPreset->defaultFileExtension(); - if (rServer->resourceByName(presetName)) { - QString currentDate = QDate::currentDate().toString(Qt::ISODate); - QString currentTime = QTime::currentTime().toString(Qt::ISODate); - QString presetFilename = saveLocation + presetName + "_backup_" + currentDate + "-" + currentTime + oldPreset->defaultFileExtension(); - oldPreset->setFilename(presetFilename); - oldPreset->setName(presetName); - oldPreset->setPresetDirty(false); - oldPreset->setValid(true); - rServer->addResource(oldPreset); - QStringList tags; - tags = rServer->assignedTagsList(curPreset.data()); - rServer->removeResourceAndBlacklist(oldPreset.data()); - Q_FOREACH (const QString & tag, tags) { - rServer->addTag(oldPreset.data(), tag); - } - } - - if (curPreset->name()==presetName) { - if (curPreset->filename().contains(saveLocation)==false || curPreset->filename().contains(presetName)==false) { - rServer->removeResourceAndBlacklist(curPreset.data()); - curPreset->setFilename(currentPresetFileName); - curPreset->setName(presetName); - } - if (!rServer->resourceByFilename(curPreset->filename())){ - //this is necessary so that we can get the preset afterwards. - rServer->addResource(curPreset, false, false); - rServer->removeFromBlacklist(curPreset.data()); - } - curPreset->setImage(m_presetsPopup->cutOutOverlay()); - curPreset->save(); - curPreset->load(); - } else { - KisPaintOpPresetSP newPreset = curPreset->clone(); - newPreset->setFilename(currentPresetFileName); - newPreset->setName(presetName); - newPreset->setImage(m_presetsPopup->cutOutOverlay()); - newPreset->setPresetDirty(false); - newPreset->setValid(true); - rServer->addResource(newPreset); - curPreset = newPreset; //to load the new preset - } - - // HACK ALERT! the server does not notify the observers - // automatically, so we need to call theupdate manually! - rServer->tagCategoryMembersChanged(); - - restoreResource(curPreset.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); slotUpdatePreset(); m_presetsEnabled = true; } else { setWidgetState(DISABLE_PRESETS); m_presetsEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) { setWidgetState(ENABLE_SIZE | ENABLE_FLOW); } else { setWidgetState(DISABLE_SIZE | DISABLE_FLOW); } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; 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_paintop_box.h b/libs/ui/kis_paintop_box.h index 1b589bfcf1..c79d954734 100644 --- a/libs/ui/kis_paintop_box.h +++ b/libs/ui/kis_paintop_box.h @@ -1,253 +1,255 @@ /* * kis_paintop_box.h - part of KImageShop/Krayon/Krita * * Copyright (c) 2004-2008 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_PAINTOP_BOX_H_ #define KIS_PAINTOP_BOX_H_ #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include "kis_signal_auto_connection.h" #include "kis_signal_compressor.h" class QToolButton; class QString; class QHBoxLayout; class KoColorSpace; class KoResource; class KoCanvasController; class KisViewManager; class KisCanvasResourceProvider; class KisPopupButton; class KisToolOptionsPopup; class KisPaintOpPresetsPopup; class KisPaintOpPresetsChooserPopup; class KisPaintOpConfigWidget; class KisCompositeOpComboBox; class KisWidgetChooser; class KisFavoriteResourceManager; class KisAction; +class KisPresetSaveWidget; /** * This widget presents all paintops that a user can paint with. * Paintops represent real-world tools or the well-known Shoup * computer equivalents that do nothing but change color. * * To incorporate the dirty preset functionality and locked settings * the following slots are added * void slotReloadPreset(); void slotGuiChangedCurrentPreset(); void slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p); void slotDropLockedOption(KisPropertiesConfigurationSP p); void slotDirtyPresetToggled(bool); Everytime a value is changed in a preset, the preset is made dirty through the onChange() function. For Locked Settings however, a changed Locked Setting will not cause a preset to become dirty. That is becuase it borrows its values from the KisLockedPropertiesServer. Hence the dirty state of the Preset is kept consistent before and after a writeConfiguration operation in most cases. * XXX: When we have a lot of paintops, replace the listbox * with a table, and for every category a combobox. * * XXX: instead of text, use pretty pictures. */ class KRITAUI_EXPORT KisPaintopBox : public QWidget { Q_OBJECT enum { ENABLE_PRESETS = 0x0001, DISABLE_PRESETS = 0x0002, ENABLE_COMPOSITEOP = 0x0004, DISABLE_COMPOSITEOP = 0x0008, ENABLE_OPACITY = 0x0010, DISABLE_OPACITY = 0x0020, ENABLE_FLOW = 0x0040, DISABLE_FLOW = 0x0080, ENABLE_SIZE = 0x0100, DISABLE_SIZE = 0x0200, ENABLE_ALL = 0x5555, DISABLE_ALL = 0xAAAA }; public: KisPaintopBox(KisViewManager* view, QWidget* parent, const char* name); ~KisPaintopBox() override; void restoreResource(KoResource* resource); /** * Update the option widgets to the argument ones, removing the currently set widgets. */ void newOptionWidgets(const QList > & optionWidgetList); KisFavoriteResourceManager *favoriteResourcesManager() { return m_favoriteResourceManager; } public Q_SLOTS: void slotColorSpaceChanged(const KoColorSpace* colorSpace); void slotInputDeviceChanged(const KoInputDevice & inputDevice); void slotCanvasResourceChanged(int key, const QVariant& v); void resourceSelected(KoResource* resource); private: void setCurrentPaintop(const KoID& paintop); void setCurrentPaintop(KisPaintOpPresetSP preset); KisPaintOpPresetSP defaultPreset(const KoID& paintOp); KisPaintOpPresetSP activePreset(const KoID& paintOp); void updateCompositeOp(QString compositeOpID); void setWidgetState(int flags); void setSliderValue(const QString& sliderID, qreal value); void sliderChanged(int n); private Q_SLOTS: - void slotSaveActivePreset(); void slotUpdatePreset(); void slotSetupDefaultPreset(); void slotNodeChanged(const KisNodeSP node); void slotToggleEraseMode(bool checked); void slotSetCompositeMode(int index); void slotSetPaintop(const QString& paintOpId); void slotHorizontalMirrorChanged(bool value); void slotVerticalMirrorChanged(bool value); void slotSlider1Changed(); void slotSlider2Changed(); void slotSlider3Changed(); void slotToolChanged(KoCanvasController* canvas, int toolId); void slotPreviousFavoritePreset(); void slotNextFavoritePreset(); void slotSwitchToPreviousPreset(); void slotUnsetEraseMode(); void slotToggleAlphaLockMode(bool); void slotDisablePressureMode(bool); void slotReloadPreset(); void slotGuiChangedCurrentPreset(); void slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p); void slotDropLockedOption(KisPropertiesConfigurationSP p); void slotDirtyPresetToggled(bool); void slotEraserBrushSizeToggled(bool); void slotEraserBrushOpacityToggled(bool); void slotUpdateSelectionIcon(); void slotLockXMirrorToggle(bool); void slotLockYMirrorToggle(bool); void slotMoveToCenterMirrorX(); void slotMoveToCenterMirrorY(); void slotHideDecorationMirrorX(bool); void slotHideDecorationMirrorY(bool); void slotUpdateOptionsWidgetPopup(); private: KisCanvasResourceProvider* m_resourceProvider; QHBoxLayout* m_layout; QWidget* m_paintopWidget; KisPaintOpConfigWidget* m_optionWidget; KisPopupButton* m_toolOptionsPopupButton; + + KisPresetSaveWidget* m_savePresetWidget; KisPopupButton* m_brushEditorPopupButton; KisPopupButton* m_presetSelectorPopupButton; KisCompositeOpComboBox* m_cmbCompositeOp; QToolButton* m_eraseModeButton; QToolButton* m_alphaLockButton; QToolButton* m_disablePressureButton; QToolButton* m_hMirrorButton; QToolButton* m_vMirrorButton; KisToolOptionsPopup* m_toolOptionsPopup; KisPaintOpPresetsPopup* m_presetsPopup; KisPaintOpPresetsChooserPopup* m_presetsChooserPopup; KisViewManager* m_viewManager; KisPopupButton* m_workspaceWidget; KisWidgetChooser* m_sliderChooser[3]; QMap m_paintopOptionWidgets; KisFavoriteResourceManager* m_favoriteResourceManager; QToolButton* m_reloadButton; KisAction* m_eraseAction; KisAction* m_reloadAction; KisAction* m_disablePressureAction; QString m_currCompositeOpID; KisNodeWSP m_previousNode; KisAction* m_hMirrorAction; KisAction* m_vMirrorAction; struct TabletToolID { TabletToolID(const KoInputDevice& dev) { uniqueID = dev.uniqueTabletId(); // Only the eraser is special, and we don't look at Cursor pointer = QTabletEvent::Pen; if (dev.pointer() == QTabletEvent::Eraser) { pointer = QTabletEvent::Eraser; } } bool operator == (const TabletToolID& id) const { return pointer == id.pointer && uniqueID == id.uniqueID; } bool operator < (const TabletToolID& id) const { if (uniqueID == id.uniqueID) return pointer < id.pointer; return uniqueID < id.uniqueID; } QTabletEvent::PointerType pointer; qint64 uniqueID; }; struct TabletToolData { KoID paintOpID; KisPaintOpPresetSP preset; }; typedef QMap TabletToolMap; typedef QMap PaintOpPresetMap; TabletToolMap m_tabletToolMap; PaintOpPresetMap m_paintOpPresetMap; TabletToolID m_currTabletToolID; bool m_presetsEnabled; bool m_blockUpdate; bool m_dirtyPresetsEnabled; bool m_eraserBrushSizeEnabled; bool m_eraserBrushOpacityEnabled; KisSignalAutoConnectionsStore m_presetConnections; }; #endif //KIS_PAINTOP_BOX_H_ diff --git a/libs/ui/kis_safe_document_loader.cpp b/libs/ui/kis_safe_document_loader.cpp index 335178de4e..eb2f6cccaa 100644 --- a/libs/ui/kis_safe_document_loader.cpp +++ b/libs/ui/kis_safe_document_loader.cpp @@ -1,182 +1,182 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_safe_document_loader.h" #include #include #include #include #include #include #include "KisDocument.h" #include "kis_image.h" #include "kis_signal_compressor.h" #include "KisPart.h" Q_GLOBAL_STATIC(QFileSystemWatcher, s_fileSystemWatcher) struct KisSafeDocumentLoader::Private { Private() : fileChangedSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE), isLoading(false), fileChangedFlag(false) { } QScopedPointer doc; KisSignalCompressor fileChangedSignalCompressor; QTimer delayedLoadTimer; bool isLoading {true}; bool fileChangedFlag {false}; QString path; QString temporaryPath; qint64 initialFileSize; QDateTime initialFileTimeStamp; }; KisSafeDocumentLoader::KisSafeDocumentLoader(const QString &path, QObject *parent) : QObject(parent), m_d(new Private()) { connect(s_fileSystemWatcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); connect(s_fileSystemWatcher, SIGNAL(fileChanged(QString)), &m_d->fileChangedSignalCompressor, SLOT(start())); connect(&m_d->fileChangedSignalCompressor, SIGNAL(timeout()), SLOT(fileChangedCompressed())); connect(&m_d->delayedLoadTimer, SIGNAL(timeout()), SLOT(delayedLoadStart())); m_d->delayedLoadTimer.setSingleShot(true); m_d->delayedLoadTimer.setInterval(100 /* ms */); setPath(path); } KisSafeDocumentLoader::~KisSafeDocumentLoader() { s_fileSystemWatcher->removePath(m_d->path); delete m_d; } void KisSafeDocumentLoader::setPath(const QString &path) { if (path.isEmpty()) return; if (!m_d->path.isEmpty()) { s_fileSystemWatcher->removePath(m_d->path); } m_d->path = path; s_fileSystemWatcher->addPath(m_d->path); } void KisSafeDocumentLoader::reloadImage() { fileChangedCompressed(true); } void KisSafeDocumentLoader::fileChanged(QString path) { if (path == m_d->path) { m_d->fileChangedFlag = true; } } void KisSafeDocumentLoader::fileChangedCompressed(bool sync) { if (m_d->isLoading) return; QFileInfo initialFileInfo(m_d->path); m_d->initialFileSize = initialFileInfo.size(); m_d->initialFileTimeStamp = initialFileInfo.lastModified(); // it may happen when the file is flushed by // so other application if (!m_d->initialFileSize) return; m_d->isLoading = true; m_d->fileChangedFlag = false; m_d->temporaryPath = QDir::tempPath() + QDir::separator() + QString("krita_file_layer_copy_%1_%2.%3") .arg(QApplication::applicationPid()) .arg(qrand()) .arg(initialFileInfo.suffix()); QFile::copy(m_d->path, m_d->temporaryPath); if (!sync) { m_d->delayedLoadTimer.start(); } else { QApplication::processEvents(); delayedLoadStart(); } } void KisSafeDocumentLoader::delayedLoadStart() { QFileInfo originalInfo(m_d->path); QFileInfo tempInfo(m_d->temporaryPath); bool successfullyLoaded = false; if (!m_d->fileChangedFlag && originalInfo.size() == m_d->initialFileSize && originalInfo.lastModified() == m_d->initialFileTimeStamp && tempInfo.size() == m_d->initialFileSize) { m_d->doc.reset(KisPart::instance()->createDocument()); successfullyLoaded = m_d->doc->openUrl(QUrl::fromLocalFile(m_d->temporaryPath), - KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES); + KisDocument::DontAddToRecent); } else { dbgKrita << "File was modified externally. Restarting."; dbgKrita << ppVar(m_d->fileChangedFlag); dbgKrita << ppVar(m_d->initialFileSize); dbgKrita << ppVar(m_d->initialFileTimeStamp); dbgKrita << ppVar(originalInfo.size()); dbgKrita << ppVar(originalInfo.lastModified()); dbgKrita << ppVar(tempInfo.size()); } QFile::remove(m_d->temporaryPath); m_d->isLoading = false; if (!successfullyLoaded) { // Restart the attempt m_d->fileChangedSignalCompressor.start(); } else { KisPaintDeviceSP paintDevice = new KisPaintDevice(m_d->doc->image()->colorSpace()); KisPaintDeviceSP projection = m_d->doc->image()->projection(); paintDevice->makeCloneFrom(projection, projection->extent()); emit loadingFinished(paintDevice, m_d->doc->image()->xRes(), m_d->doc->image()->yRes()); } m_d->doc.reset(); } diff --git a/libs/ui/kis_statusbar.cc b/libs/ui/kis_statusbar.cc index 38f7e5d790..90a44efa2d 100644 --- a/libs/ui/kis_statusbar.cc +++ b/libs/ui/kis_statusbar.cc @@ -1,392 +1,395 @@ /* This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_statusbar.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_icon_utils.h" #include #include #include #include #include #include "kis_memory_statistics_server.h" #include #include "KisViewManager.h" #include "canvas/kis_canvas2.h" #include "kis_progress_widget.h" #include "kis_zoom_manager.h" #include #include #include enum { IMAGE_SIZE_ID, POINTER_POSITION_ID }; KisStatusBar::KisStatusBar(KisViewManager *view) : m_view(view), m_imageView(0), m_statusBar(0) { } void KisStatusBar::setup() { m_selectionStatus = new QToolButton(); m_selectionStatus->setIconSize(QSize(16,16)); m_selectionStatus->setAutoRaise(true); m_selectionStatus->setEnabled(false); updateSelectionIcon(); m_statusBar = m_view->mainWindow()->statusBar(); connect(m_selectionStatus, SIGNAL(clicked()), m_view->selectionManager(), SLOT(slotToggleSelectionDecoration())); connect(m_view->selectionManager(), SIGNAL(displaySelectionChanged()), SLOT(updateSelectionToolTip())); connect(m_view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(updateSelectionIcon())); addStatusBarItem(m_selectionStatus); m_selectionStatus->setVisible(false); m_statusBarStatusLabel = new KSqueezedTextLabel(); m_statusBarStatusLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_statusBarStatusLabel->setContentsMargins(5, 5, 5, 5); connect(KoToolManager::instance(), SIGNAL(changedStatusText(const QString &)), m_statusBarStatusLabel, SLOT(setText(const QString &))); addStatusBarItem(m_statusBarStatusLabel, 2); m_statusBarStatusLabel->setVisible(false); m_statusBarProfileLabel = new KSqueezedTextLabel(); m_statusBarProfileLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_statusBarProfileLabel->setContentsMargins(5, 5, 5, 5); addStatusBarItem(m_statusBarProfileLabel, 3); m_statusBarProfileLabel->setVisible(false); m_progress = new KisProgressWidget(); addStatusBarItem(m_progress); m_progress->setVisible(false); + connect(m_progress, SIGNAL(sigCancellationRequested()), this, SIGNAL(sigCancellationRequested())); + + m_progressUpdater.reset(new KisProgressUpdater(m_progress, m_progress->progressProxy())); + m_progressUpdater->setAutoNestNames(true); m_memoryReportBox = new QPushButton(); m_memoryReportBox->setFlat(true); m_memoryReportBox->setContentsMargins(5, 5, 5, 5); m_memoryReportBox->setMinimumWidth(120); addStatusBarItem(m_memoryReportBox); m_memoryReportBox->setVisible(false); connect(m_memoryReportBox, SIGNAL(clicked()), SLOT(showMemoryInfoToolTip())); m_pointerPositionLabel = new QLabel(QString()); m_pointerPositionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pointerPositionLabel->setMinimumWidth(100); m_pointerPositionLabel->setContentsMargins(5,5, 5, 5); addStatusBarItem(m_pointerPositionLabel); m_pointerPositionLabel->setVisible(false); connect(KisMemoryStatisticsServer::instance(), SIGNAL(sigUpdateMemoryStatistics()), SLOT(imageSizeChanged())); } KisStatusBar::~KisStatusBar() { } void KisStatusBar::setView(QPointer imageView) { if (m_imageView == imageView) { return; } if (m_imageView) { m_imageView->disconnect(this); removeStatusBarItem(m_imageView->zoomManager()->zoomActionWidget()); m_imageView = 0; } if (imageView) { m_imageView = imageView; connect(m_imageView, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SLOT(updateStatusBarProfileLabel())); connect(m_imageView, SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SLOT(updateStatusBarProfileLabel())); connect(m_imageView, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), this, SLOT(imageSizeChanged())); updateStatusBarProfileLabel(); addStatusBarItem(m_imageView->zoomManager()->zoomActionWidget()); } imageSizeChanged(); } void KisStatusBar::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { StatusBarItem sbItem(widget); if (permanent) { m_statusBar->addPermanentWidget(widget, stretch); } else { m_statusBar->addWidget(widget, stretch); } sbItem.show(); m_statusBarItems.append(sbItem); } void KisStatusBar::removeStatusBarItem(QWidget *widget) { int i = 0; Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { if (sbItem.widget() == widget) { break; } i++; } if (i < m_statusBarItems.count()) { m_statusBar->removeWidget(m_statusBarItems[i].widget()); m_statusBarItems.remove(i); } } void KisStatusBar::hideAllStatusBarItems() { Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { sbItem.hide(); } } void KisStatusBar::showAllStatusBarItems() { Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { sbItem.show(); } } void KisStatusBar::documentMousePositionChanged(const QPointF &pos) { if (!m_imageView) return; QPoint pixelPos = m_imageView->image()->documentToIntPixel(pos); pixelPos.setX(qBound(0, pixelPos.x(), m_view->image()->width() - 1)); pixelPos.setY(qBound(0, pixelPos.y(), m_view->image()->height() - 1)); m_pointerPositionLabel->setText(QString("%1, %2").arg(pixelPos.x()).arg(pixelPos.y())); } void KisStatusBar::imageSizeChanged() { updateMemoryStatus(); QString sizeText; KisImageWSP image = m_imageView ? m_imageView->image() : 0; if (image) { qint32 w = image->width(); qint32 h = image->height(); sizeText = QString("%1 x %2 (%3)").arg(w).arg(h).arg(m_shortMemoryTag); } else { sizeText = m_shortMemoryTag; } m_memoryReportBox->setIcon(m_memoryStatusIcon); m_memoryReportBox->setText(sizeText); m_memoryReportBox->setToolTip(m_longMemoryTag); } void KisStatusBar::updateSelectionIcon() { QIcon icon; if (!m_view->selectionManager()->displaySelection()) { icon = KisIconUtils::loadIcon("selection-mode_invisible"); } else if (m_view->selectionManager()->showSelectionAsMask()) { icon = KisIconUtils::loadIcon("selection-mode_mask"); } else /* if (!m_view->selectionManager()->showSelectionAsMask()) */ { icon = KisIconUtils::loadIcon("selection-mode_ants"); } m_selectionStatus->setIcon(icon); } inline QString formatSize(qint64 size) { qint64 K = 1024; QString suffix = i18nc("very shortened \'byte\' suffix (for statusbar)", "b"); qreal realSize = size; if (realSize > K) { realSize /= K; suffix = i18nc("very shortened KiB suffix (for statusbar)", "K"); } if (realSize > K) { realSize /= K; suffix = i18nc("very shortened MiB suffix (for statusbar)", "M"); } if (realSize > K) { realSize /= K; suffix = i18nc("very shortened GiB suffix (for statusbar)", "G"); } if (realSize > K) { realSize /= K; suffix = i18nc("very shortened TiB suffix (for statusbar)", "T"); } return QString("%2%3").arg(QString::number(realSize, 'f', 1)).arg(suffix); } void KisStatusBar::updateMemoryStatus() { KisMemoryStatisticsServer::Statistics stats = KisMemoryStatisticsServer::instance() ->fetchMemoryStatistics(m_imageView ? m_imageView->image() : 0); QString longStats = i18nc("tooltip on statusbar memory reporting button", "Image size:\t %1\n" "\n" "Memory used:\t %2 / %3\n" " image data:\t %4 / %5\n" " pool:\t\t %6 / %7\n" " undo data:\t %8\n" "\n" "Swap used:\t %9", formatSize(stats.imageSize), formatSize(stats.totalMemorySize), formatSize(stats.totalMemoryLimit), formatSize(stats.realMemorySize), formatSize(stats.tilesHardLimit), formatSize(stats.poolSize), formatSize(stats.tilesPoolLimit), formatSize(stats.historicalMemorySize), formatSize(stats.swapSize)); QString shortStats = formatSize(stats.imageSize); QIcon icon; qint64 warnLevel = stats.tilesHardLimit - stats.tilesHardLimit / 8; if (stats.imageSize > warnLevel || stats.realMemorySize > warnLevel) { icon = KisIconUtils::loadIcon("dialog-warning"); QString suffix = i18nc("tooltip on statusbar memory reporting button", "\n\nWARNING:\tOut of memory! Swapping has been started.\n" "\t\tPlease configure more RAM for Krita in Settings dialog"); longStats += suffix; } m_shortMemoryTag = shortStats; m_longMemoryTag = longStats; m_memoryStatusIcon = icon; } void KisStatusBar::showMemoryInfoToolTip() { QToolTip::showText(QCursor::pos(), m_memoryReportBox->toolTip(), m_memoryReportBox); } void KisStatusBar::updateSelectionToolTip() { updateSelectionIcon(); KisSelectionSP selection = m_view->selection(); if (selection) { m_selectionStatus->setEnabled(true); QRect r = selection->selectedExactRect(); QString displayMode = !m_view->selectionManager()->displaySelection() ? i18n("Hidden") : (m_view->selectionManager()->showSelectionAsMask() ? i18n("Mask") : i18n("Ants")); m_selectionStatus->setToolTip( i18n("Selection: x = %1 y = %2 width = %3 height = %4\n" "Display Mode: %5", r.x(), r.y(), r.width(), r.height(), displayMode)); } else { m_selectionStatus->setEnabled(false); m_selectionStatus->setToolTip(i18n("No Selection")); } } void KisStatusBar::setSelection(KisImageWSP image) { Q_UNUSED(image); updateSelectionToolTip(); } void KisStatusBar::setProfile(KisImageWSP image) { if (m_statusBarProfileLabel == 0) { return; } if (!image) return; if (image->profile() == 0) { m_statusBarProfileLabel->setText(i18n("No profile")); } else { m_statusBarProfileLabel->setText(image->colorSpace()->name() + " " + image->profile()->name()); } } void KisStatusBar::setHelp(const QString &t) { Q_UNUSED(t); } void KisStatusBar::updateStatusBarProfileLabel() { if (!m_imageView) return; setProfile(m_imageView->image()); } - -KisProgressWidget* KisStatusBar::progress() +KoProgressUpdater *KisStatusBar::progressUpdater() { - return m_progress; + return m_progressUpdater.data(); } diff --git a/libs/ui/kis_statusbar.h b/libs/ui/kis_statusbar.h index acb5ed1c1e..8363873da6 100644 --- a/libs/ui/kis_statusbar.h +++ b/libs/ui/kis_statusbar.h @@ -1,129 +1,134 @@ /* This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 2003-200^ 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_STATUSBAR_H #define KIS_STATUSBAR_H #include #include #include #include #include #include "KisView.h" class QLabel; class QToolButton; class QPushButton; class KSqueezedTextLabel; class KisViewManager; class KisProgressWidget; +class KoProgressUpdater; #include "kritaui_export.h" class KRITAUI_EXPORT KisStatusBar : public QObject { class StatusBarItem { public: StatusBarItem() // for QValueList : m_widget(0) {} StatusBarItem(QWidget * widget) : m_widget(widget) {} bool operator==(const StatusBarItem& rhs) { return m_widget == rhs.m_widget; } bool operator!=(const StatusBarItem& rhs) { return m_widget != rhs.m_widget; } QWidget * widget() const { return m_widget; } void show() const { m_widget->show(); } void hide() const { m_widget->hide(); } private: QPointer m_widget; }; Q_OBJECT public: KisStatusBar(KisViewManager *view); ~KisStatusBar() override; void setup(); void setView(QPointer imageView); void addStatusBarItem(QWidget *widget, int stretch = 0, bool permanent = false); void removeStatusBarItem(QWidget *widget); void hideAllStatusBarItems(); void showAllStatusBarItems(); - KisProgressWidget *progress(); + KoProgressUpdater *progressUpdater(); public Q_SLOTS: void documentMousePositionChanged(const QPointF &p); void imageSizeChanged(); void setSelection(KisImageWSP image); void setProfile(KisImageWSP image); void setHelp(const QString &t); void updateStatusBarProfileLabel(); void updateSelectionToolTip(); private Q_SLOTS: void updateSelectionIcon(); void showMemoryInfoToolTip(); +Q_SIGNALS: + void sigCancellationRequested(); + private: void updateMemoryStatus(); private: QPointer m_view; QPointer m_imageView; QPointer m_statusBar; - KisProgressWidget * m_progress; + KisProgressWidget *m_progress; + QScopedPointer m_progressUpdater; QToolButton *m_selectionStatus; QPushButton *m_memoryReportBox; QLabel *m_pointerPositionLabel; KSqueezedTextLabel *m_statusBarStatusLabel; KSqueezedTextLabel *m_statusBarProfileLabel; QString m_shortMemoryTag; QString m_longMemoryTag; QIcon m_memoryStatusIcon; QVector m_statusBarItems; }; #endif diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index 18578809eb..e3ddf4dafd 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,225 +1,233 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "opengl/kis_opengl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { + bool defaultFormatIsSet = false; bool initialized = false; bool NeedsFenceWorkaround = false; bool NeedsPixmapCacheWorkaround = false; int glMajorVersion = 0; int glMinorVersion = 0; bool supportsDeprecatedFunctions = false; QString Renderer; } void KisOpenGL::initialize() { if (initialized) return; - setDefaultFormat(); + if (!defaultFormatIsSet) { + qWarning() << "Default OpenGL format was not set before calling KisOpenGL::initialize. This might be a BUG!"; + setDefaultFormat(); + } // 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 Renderer = QString((const char*)funcs->glGetString(GL_RENDERER)); /** * Warn about Intel's broken video drivers */ #if defined Q_OS_WIN KisConfig cfg; if (cfg.useOpenGL() && Renderer.startsWith("Intel") && !cfg.readEntry("WarnedAboutIntel", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You have an Intel(R) HD Graphics video adapter.\n" "If you experience problems like a crash, a black or blank screen," "please update your display driver to the latest version.\n\n" "If Krita crashes, it will disable OpenGL rendering. Please restart Krita in that case.\n After updating your drivers you can re-enable OpenGL in Krita's Settings.\n")); cfg.writeEntry("WarnedAboutIntel", true); } #endif qDebug() << "OpenGL Info"; qDebug() << " Vendor: " << reinterpret_cast(funcs->glGetString(GL_VENDOR)); qDebug() << " Renderer: " << Renderer; qDebug() << " Version: " << reinterpret_cast(funcs->glGetString(GL_VERSION)); qDebug() << " Shading language: " << reinterpret_cast(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION)); qDebug() << " Requested format: " << QSurfaceFormat::defaultFormat(); qDebug() << " Current format: " << context.format(); glMajorVersion = context.format().majorVersion(); glMinorVersion = context.format().minorVersion(); supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); qDebug() << " Version:" << glMajorVersion << "." << glMinorVersion; qDebug() << " Supports deprecated functions" << supportsDeprecatedFunctions; initialized = true; } void KisOpenGL::initializeContext(QOpenGLContext *ctx) { KisConfig cfg; initialize(); dbgUI << "OpenGL: Opening new context"; // Double check we were given the version we requested QSurfaceFormat format = ctx->format(); QOpenGLFunctions *f = ctx->functions(); f->initializeOpenGLFunctions(); QFile log(QDesktopServices::storageLocation(QDesktopServices::TempLocation) + "/krita-opengl.txt"); log.open(QFile::WriteOnly); QString vendor((const char*)f->glGetString(GL_VENDOR)); log.write(vendor.toLatin1()); log.write(", "); log.write(Renderer.toLatin1()); log.write(", "); QString version((const char*)f->glGetString(GL_VERSION)); log.write(version.toLatin1()); log.close(); // Check if we have a bugged driver that needs fence workaround bool isOnX11 = false; #ifdef HAVE_X11 isOnX11 = true; #endif if ((isOnX11 && Renderer.startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) { NeedsFenceWorkaround = true; } /** * NVidia + Qt's openGL don't play well together and one cannot * draw a pixmap on a widget more than once in one rendering cycle. * * It can be workarounded by drawing strictly via QPixmapCache and * only when the pixmap size in bigger than doubled size of the * display framebuffer. That is for 8-bit HD display, you should have * a cache bigger than 16 MiB. Don't ask me why. (DK) * * See bug: https://bugs.kde.org/show_bug.cgi?id=361709 * * TODO: check if this workaround is still needed after merging * Qt5+openGL3 branch. */ if (vendor.toUpper().contains("NVIDIA")) { NeedsPixmapCacheWorkaround = true; const QRect screenSize = QApplication::desktop()->screenGeometry(); const int minCacheSize = 20 * 1024; const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize)); } } // XXX Temporary function to allow LoD on OpenGL3 without triggering // all of the other 3.2 functionality, can be removed once we move to Qt5.7 bool KisOpenGL::supportsLoD() { initialize(); return (glMajorVersion * 100 + glMinorVersion) >= 300; } bool KisOpenGL::hasOpenGL3() { initialize(); return (glMajorVersion * 100 + glMinorVersion) >= 302; } bool KisOpenGL::supportsFenceSync() { initialize(); return glMajorVersion >= 3; } bool KisOpenGL::needsFenceWorkaround() { initialize(); return NeedsFenceWorkaround; } bool KisOpenGL::needsPixmapCacheWorkaround() { initialize(); return NeedsPixmapCacheWorkaround; } void KisOpenGL::setDefaultFormat() { + if (defaultFormatIsSet) { + return; + } + defaultFormatIsSet = true; QSurfaceFormat format; #ifdef Q_OS_OSX format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #else // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing QSurfaceFormat::setDefaultFormat(format); } bool KisOpenGL::hasOpenGL() { return ((glMajorVersion * 100 + glMinorVersion) >= 201); //return (glMajorVersion >= 3 && supportsDeprecatedFunctions); } diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index 80238a9f00..5ee1d30fb3 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,860 +1,860 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006-2013 * 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. */ #define GL_GLEXT_PROTOTYPES #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl_canvas2_p.h" #include "opengl/kis_opengl_shader_loader.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_display_filter.h" #include "canvas/kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 static bool OPENGL_SUCCESS = false; struct KisOpenGLCanvas2::Private { public: ~Private() { delete displayShader; delete checkerShader; delete solidColorShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; GLfloat checkSizeScale; bool scrollCheckers; QSharedPointer displayFilter; KisOpenGL::FilterMode filterMode; bool proofingConfigIsUpdated=false; GLsync glSyncObject{0}; bool wrapAroundMode{false}; // Stores a quad for drawing the canvas QOpenGLVertexArrayObject quadVAO; QOpenGLBuffer quadBuffers[2]; // Stores data for drawing tool outlines QOpenGLVertexArrayObject outlineVAO; QOpenGLBuffer lineBuffer; QVector3D vertices[6]; QVector2D texCoords[6]; int xToColWithWrapCompensation(int x, const QRect &imageRect) { int firstImageColumn = openGLImageTextures->xToCol(imageRect.left()); int lastImageColumn = openGLImageTextures->xToCol(imageRect.right()); int colsPerImage = lastImageColumn - firstImageColumn + 1; int numWraps = floor(qreal(x) / imageRect.width()); int remainder = x - imageRect.width() * numWraps; return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder); } int yToRowWithWrapCompensation(int y, const QRect &imageRect) { int firstImageRow = openGLImageTextures->yToRow(imageRect.top()); int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom()); int rowsPerImage = lastImageRow - firstImageRow + 1; int numWraps = floor(qreal(y) / imageRect.height()); int remainder = y - imageRect.height() * numWraps; return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter) : QOpenGLWidget(parent) , KisCanvasWidgetBase(canvas, coordinatesConverter) , d(new Private()) { KisConfig cfg; cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->monitorProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_OSX setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, true); setAttribute(Qt::WA_DontCreateNativeAncestors, true); setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } bool KisOpenGLCanvas2::needsFpsDebugging() const { return KisOpenglCanvasDebugger::instance()->showFpsOnCanvas(); } void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer displayFilter) { setDisplayFilterImpl(displayFilter, false); } void KisOpenGLCanvas2::setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing) { bool needsInternalColorManagement = !displayFilter || displayFilter->useInternalColorManagement(); bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement); d->displayFilter = displayFilter; if (d->canvasInitialized) { d->canvasInitialized = false; delete d->displayShader; bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; try { d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } d->canvasInitialized = true; } if (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { d->wrapAroundMode = value; update(); } inline void rectToVertices(QVector3D* vertices, const QRectF &rc) { vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f); vertices[1] = QVector3D(rc.left(), rc.top(), 0.f); vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f); vertices[3] = QVector3D(rc.left(), rc.top(), 0.f); vertices[4] = QVector3D(rc.right(), rc.top(), 0.f); vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f); } inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc) { texCoords[0] = QVector2D(rc.left(), rc.bottom()); texCoords[1] = QVector2D(rc.left(), rc.top()); texCoords[2] = QVector2D(rc.right(), rc.bottom()); texCoords[3] = QVector2D(rc.left(), rc.top()); texCoords[4] = QVector2D(rc.right(), rc.top()); texCoords[5] = QVector2D(rc.right(), rc.bottom()); } void KisOpenGLCanvas2::initializeGL() { KisOpenGL::initializeContext(context()); initializeOpenGLFunctions(); KisConfig cfg; d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); d->openGLImageTextures->initGL(context()->functions()); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); initializeShaders(); // If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing if (KisOpenGL::hasOpenGL3()) { d->quadVAO.create(); d->quadVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // Create the vertex buffer object, it has 6 vertices with 3 components d->quadBuffers[0].create(); d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[0].bind(); d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); // Create the texture buffer object, it has 6 texture coordinates with 2 components d->quadBuffers[1].create(); d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[1].bind(); d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); // Create the outline buffer, this buffer will store the outlines of // tools and will frequently change data d->outlineVAO.create(); d->outlineVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly d->lineBuffer.create(); d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); d->lineBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); } Sync::init(context()); d->canvasInitialized = true; } /** * Loads all shaders and reports compilation problems */ void KisOpenGLCanvas2::initializeShaders() { bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; if (!d->canvasInitialized) { delete d->displayShader; delete d->checkerShader; delete d->solidColorShader; try { d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); d->checkerShader = d->shaderLoader.loadCheckerShader(); d->solidColorShader = d->shaderLoader.loadSolidColorShader(); } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } } } /** * Displays a message box telling the user that * shader compilation failed and turns off OpenGL. */ void KisOpenGLCanvas2::reportFailedShaderCompilation(const QString &context) { KisConfig cfg; if (cfg.useVerboseOpenGLDebugOutput()) { dbgUI << "GL-log:" << context; } qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), QString(i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.")).arg(context), QMessageBox::Close); cfg.setUseOpenGL(false); cfg.setCanvasState("OPENGL_FAILED"); } void KisOpenGLCanvas2::resizeGL(int width, int height) { coordinatesConverter()->setCanvasWidgetSize(QSize(width, height)); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); renderCanvasGL(); if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); QPainter gc(this); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { if (!d->solidColorShader->bind()) { return; } // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); glLogicOp(GL_XOR); KisConfig cfg; QColor cursorColor = cfg.getCursorMainColor(); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(cursorColor.redF(), cursorColor.greenF(), cursorColor.blueF(), 1.0f)); // Paint the tool outline if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } // Convert every disjointed subpath to a polygon and draw that polygon QList subPathPolygons = path.toSubpathPolygons(); for (int i = 0; i < subPathPolygons.size(); i++) { const QPolygonF& polygon = subPathPolygons.at(i); QVector vertices; vertices.resize(polygon.count()); for (int j = 0; j < polygon.count(); j++) { QPointF p = polygon.at(j); vertices[j].setX(p.x()); vertices[j].setY(p.y()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); } glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } glDisable(GL_COLOR_LOGIC_OP); d->solidColorShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(this->rect()); converter->getOpenGLCheckersInfo(viewportRect, &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers); textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE, d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE); if (!d->checkerShader->bind()) { qWarning() << "Could not bind checker shader"; return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix(textureTransform); d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } // render checkers glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture()); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); d->checkerShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::drawGrid() { if (!d->solidColorShader->bind()) { return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisConfig cfg; - QColor gridColor = cfg.getOpenGLGridColor(); + QColor gridColor = cfg.getPixelGridColor(); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(gridColor.redF(), gridColor.greenF(), gridColor.blueF(), 0.5f)); if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } QRectF widgetRect(0,0, width(), height()); QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect)); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { wr &= d->openGLImageTextures->storedImageBounds(); } QPoint topLeftCorner = wr.topLeft(); QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1); QVector grid; for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) { grid.append(QVector3D(i, topLeftCorner.y(), 0)); grid.append(QVector3D(i, bottomRightCorner.y(), 0)); } for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) { grid.append(QVector3D(topLeftCorner.x(), i, 0)); grid.append(QVector3D(bottomRightCorner.x(), i, 0)); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData()); } glDrawArrays(GL_LINES, 0, grid.size()); if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } d->solidColorShader->release(); } void KisOpenGLCanvas2::drawImage() { if (!d->displayShader) { return; } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisCoordinatesConverter *converter = coordinatesConverter(); d->displayShader->bind(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix; textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); QRectF widgetRect(0,0, width(), height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); qreal scaleX, scaleY; converter->imageScale(&scaleX, &scaleY); d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { // if we don't want to paint wrapping images, just limit the // processing area, and the code will handle all the rest wr &= ir; } int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); int minColumn = d->openGLImageTextures->xToCol(ir.left()); int maxColumn = d->openGLImageTextures->xToCol(ir.right()); int minRow = d->openGLImageTextures->yToRow(ir.top()); int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); int imageColumns = maxColumn - minColumn + 1; int imageRows = maxRow - minRow + 1; for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { int effectiveCol = col; int effectiveRow = row; QPointF tileWrappingTranslation; if (effectiveCol > maxColumn || effectiveCol < minColumn) { int translationStep = floor(qreal(col) / imageColumns); int originCol = translationStep * imageColumns; effectiveCol = col - originCol; tileWrappingTranslation.rx() = translationStep * ir.width(); } if (effectiveRow > maxRow || effectiveRow < minRow) { int translationStep = floor(qreal(row) / imageRows); int originRow = translationStep * imageRows; effectiveRow = row - originRow; tileWrappingTranslation.ry() = translationStep * ir.height(); } KisTextureTile *tile = d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow); if (!tile) { warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; continue; } /* * We create a float rect here to workaround Qt's * "history reasons" in calculation of right() * and bottom() coordinates of integer rects. */ QRectF textureRect(tile->tileRectInTexturePixels()); QRectF modelRect(tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y())); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } if (d->displayFilter) { glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); } int currentLodPlane = tile->currentLodPlane(); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } glActiveTexture(GL_TEXTURE0); tile->bindToActiveTexture(); if (currentLodPlane > 0) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); switch(d->filterMode) { case KisOpenGL::NearestFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case KisOpenGL::BilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case KisOpenGL::TrilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break; case KisOpenGL::HighQualityFiltering: if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } break; } } glDrawArrays(GL_TRIANGLES, 0, 6); } } glBindTexture(GL_TEXTURE_2D, 0); d->displayShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg; d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast(cfg.checkSize()); d->scrollCheckers = cfg.scrollCheckers(); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels()); d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode(); notifyConfigChanged(); } QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const { return processInputMethodQuery(query); } void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event) { processInputMethodEvent(event); } void KisOpenGLCanvas2::renderCanvasGL() { // Draw the border (that is, clear the whole widget to the border color) QColor widgetBackgroundColor = borderColor(); glClearColor(widgetBackgroundColor.redF(), widgetBackgroundColor.greenF(), widgetBackgroundColor.blueF(), 1.0); glClear(GL_COLOR_BUFFER_BIT); if (d->displayFilter) { d->displayFilter->updateShader(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.bind(); } drawCheckers(); drawImage(); KisConfig cfg; - if (coordinatesConverter()->effectiveZoom() > cfg.getOpenGLGridDrawingThreshold() - 0.00001) { + if ((coordinatesConverter()->effectiveZoom() > cfg.getPixelGridDrawingThreshold() - 0.00001) && cfg.pixelGridEnabled()) { drawGrid(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.release(); } } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayProfile(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->monitorProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); } void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); } void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h) { if (d->canvasInitialized) { d->openGLImageTextures->slotImageSizeChanged(w, h); } } KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); if (canvas()->proofingConfigUpdated()) { d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); canvas()->setProofingConfigUpdated(false); } return d->openGLImageTextures->updateCache(rc); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info); } #ifdef Q_OS_OSX /** * There is a bug on OSX: if we issue frame redraw before the tiles finished * uploading, the tiles will become corrupted. Depending on the GPU/driver * version either the tile itself, or its mipmaps will become totally * transparent. */ glFinish(); #endif return QRect(); // FIXME: Implement dirty rect for OpenGL } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/tests/kis_animation_exporter_test.cpp b/libs/ui/tests/kis_animation_exporter_test.cpp index f2e2a9751a..8fc3806892 100644 --- a/libs/ui/tests/kis_animation_exporter_test.cpp +++ b/libs/ui/tests/kis_animation_exporter_test.cpp @@ -1,99 +1,100 @@ /* * 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_exporter_test.h" #include "kis_animation_exporter.h" #include #include #include "KisPart.h" #include "kis_image.h" #include "KisDocument.h" #include "kis_image_animation_interface.h" #include "KoColor.h" +#include #include "kis_time_range.h" #include "kis_keyframe_channel.h" void KisAnimationExporterTest::testAnimationExport() { KisDocument *document = KisPart::instance()->createDocument(); QRect rect(0,0,512,512); QRect fillRect(10,0,502,512); TestUtil::MaskParent p(rect); document->setCurrentImage(p.image); const KoColorSpace *cs = p.image->colorSpace(); KUndo2Command parentCommand; p.layer->enableAnimation(); KisKeyframeChannel *rasterChannel = p.layer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); rasterChannel->addKeyframe(1, &parentCommand); rasterChannel->addKeyframe(2, &parentCommand); p.image->animationInterface()->setFullClipRange(KisTimeRange::fromTime(0, 2)); KisPaintDeviceSP dev = p.layer->paintDevice(); dev->fill(fillRect, KoColor(Qt::red, cs)); QImage frame0 = dev->convertToQImage(0, rect); p.image->animationInterface()->switchCurrentTimeAsync(1); p.image->waitForDone(); dev->fill(fillRect, KoColor(Qt::green, cs)); QImage frame1 = dev->convertToQImage(0, rect); p.image->animationInterface()->switchCurrentTimeAsync(2); p.image->waitForDone(); dev->fill(fillRect, KoColor(Qt::blue, cs)); QImage frame2 = dev->convertToQImage(0, rect); KisAnimationExportSaver exporter(document, "export-test.png", 0, 2); QSignalSpy spy(document, SIGNAL(sigProgress(int))); QVERIFY(spy.isValid()); exporter.exportAnimation(); // If we had Qt5 already: // spy.wait(100); // spy.wait(100); // spy.wait(100); // But in the meanwhile... QTest::qWait(1000); //QCOMPARE(spy.count(), 3); //QCOMPARE(spy.at(0).at(0).value(), 0); //QCOMPARE(spy.at(1).at(0).value(), 1); //QCOMPARE(spy.at(2).at(0).value(), 2); // FIXME: Export doesn't seem to work from unit tests QImage exported; exported.load("export-test0000.png"); QCOMPARE(exported, frame0); exported.load("export-test0001.png"); QCOMPARE(exported, frame1); exported.load("export-test0002.png"); QCOMPARE(exported, frame2); } QTEST_MAIN(KisAnimationExporterTest) diff --git a/libs/ui/tests/kis_animation_importer_test.cpp b/libs/ui/tests/kis_animation_importer_test.cpp index dfdbbd12a4..234c963be2 100644 --- a/libs/ui/tests/kis_animation_importer_test.cpp +++ b/libs/ui/tests/kis_animation_importer_test.cpp @@ -1,79 +1,80 @@ /* * 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_importer_test.h" #include "KisPart.h" #include "kis_animation_importer.h" #include "KisDocument.h" #include "testutil.h" #include "kis_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "kis_group_layer.h" +#include void KisAnimationImporterTest::testImport() { KisDocument *document = KisPart::instance()->createDocument(); TestUtil::MaskParent mp(QRect(0,0,512,512)); document->setCurrentImage(mp.image); KisAnimationImporter importer(document->image()); QStringList files; files.append(QString(FILES_DATA_DIR) + QDir::separator() + "file_layer_source.png"); files.append(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); files.append(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); importer.import(files, 7, 3); KisGroupLayerSP root = mp.image->rootLayer(); KisNodeSP importedLayer = root->lastChild(); KisKeyframeChannel* contentChannel = importedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(contentChannel != 0); QCOMPARE(contentChannel->keyframeCount(), 4); // Three imported ones + blank at time 0 QVERIFY(!contentChannel->keyframeAt(7).isNull()); QVERIFY(!contentChannel->keyframeAt(10).isNull()); QVERIFY(!contentChannel->keyframeAt(13).isNull()); mp.image->animationInterface()->switchCurrentTimeAsync(7); mp.image->waitForDone(); QImage imported1 = importedLayer->projection()->convertToQImage(importedLayer->colorSpace()->profile()); mp.image->animationInterface()->switchCurrentTimeAsync(10); mp.image->waitForDone(); QImage imported2 = importedLayer->projection()->convertToQImage(importedLayer->colorSpace()->profile()); mp.image->animationInterface()->switchCurrentTimeAsync(13); mp.image->waitForDone(); QImage imported3 = importedLayer->projection()->convertToQImage(importedLayer->colorSpace()->profile()); QImage source1(QString(FILES_DATA_DIR) + QDir::separator() + "file_layer_source.png"); QImage source2(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QImage source3(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); QPoint pt; QVERIFY(TestUtil::compareQImages(pt, source1, imported1)); QVERIFY(TestUtil::compareQImages(pt, source2, imported2)); QVERIFY(TestUtil::compareQImages(pt, source3, imported3)); delete document; } QTEST_MAIN(KisAnimationImporterTest) diff --git a/libs/ui/tool/kis_delegated_tool.h b/libs/ui/tool/kis_delegated_tool.h index d592f69dd5..bac5d3de71 100644 --- a/libs/ui/tool/kis_delegated_tool.h +++ b/libs/ui/tool/kis_delegated_tool.h @@ -1,141 +1,144 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_DELEGATED_TOOL_H #define __KIS_DELEGATED_TOOL_H #include #include #include #include "input/kis_input_manager.h" #include "canvas/kis_canvas2.h" #include "kis_delegated_tool_policies.h" +#include "kis_tool.h" #define PRESS_CONDITION_OM(_event, _mode, _button, _modifier) \ (this->mode() == (_mode) && (_event)->button() == (_button) && \ ((_event)->modifiers() & (_modifier) || \ (_event)->modifiers() == Qt::NoModifier)) template class KisDelegatedTool : public BaseClass { public: KisDelegatedTool(KoCanvasBase *canvas, const QCursor &cursor, DelegateTool *delegateTool) : BaseClass(canvas, cursor), m_localTool(delegateTool) { } DelegateTool* localTool() const { return m_localTool.data(); } void activate(typename BaseClass::ToolActivation toolActivation, const QSet &shapes) override { BaseClass::activate(toolActivation, shapes); m_localTool->activate(toolActivation, shapes); ActivationPolicy::onActivate(BaseClass::canvas()); KisInputManager *inputManager = (static_cast(BaseClass::canvas()))->globalInputManager(); if (inputManager) { inputManager->attachPriorityEventFilter(this); } } void deactivate() override { m_localTool->deactivate(); BaseClass::deactivate(); KisInputManager *inputManager = (static_cast(BaseClass::canvas()))->globalInputManager(); if (inputManager) { inputManager->detachPriorityEventFilter(this); } } void mousePressEvent(KoPointerEvent *event) override { if(PRESS_CONDITION_OM(event, KisTool::HOVER_MODE, Qt::LeftButton, Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier)) { this->setMode(KisTool::PAINT_MODE); Q_ASSERT(m_localTool); m_localTool->mousePressEvent(event); } else { BaseClass::mousePressEvent(event); } } void mouseDoubleClickEvent(KoPointerEvent *event) override { if(PRESS_CONDITION_OM(event, KisTool::HOVER_MODE, Qt::LeftButton, Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier)) { Q_ASSERT(m_localTool); m_localTool->mouseDoubleClickEvent(event); } else { BaseClass::mouseDoubleClickEvent(event); } } void mouseMoveEvent(KoPointerEvent *event) override { Q_ASSERT(m_localTool); m_localTool->mouseMoveEvent(event); BaseClass::mouseMoveEvent(event); } void mouseReleaseEvent(KoPointerEvent *event) override { if (this->mode() == KisTool::PAINT_MODE && event->button() == Qt::LeftButton) { + this->setMode(KisTool::HOVER_MODE); + Q_ASSERT(m_localTool); m_localTool->mouseReleaseEvent(event); } else { BaseClass::mouseReleaseEvent(event); } } void paint(QPainter &painter, const KoViewConverter &converter) override { Q_ASSERT(m_localTool); m_localTool->paint(painter, converter); } QList > createOptionWidgets() override { QList > list = BaseClass::createOptionWidgets(); list.append(m_localTool->createOptionWidgets()); return list; } protected: QScopedPointer m_localTool; }; #endif /* __KIS_DELEGATED_TOOL_H */ diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc index 191ba27918..56db552916 100644 --- a/libs/ui/tool/kis_tool.cc +++ b/libs/ui/tool/kis_tool.cc @@ -1,695 +1,695 @@ /* * Copyright (c) 2006, 2010 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl_canvas2.h" #include "kis_canvas_resource_provider.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "filter/kis_filter_configuration.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include #include #include "kis_resources_snapshot.h" #include #include "kis_action_registry.h" #include "kis_tool_utils.h" struct Q_DECL_HIDDEN KisTool::Private { QCursor cursor; // the cursor that should be shown on tool activation. // From the canvas resources KoPattern* currentPattern{0}; KoAbstractGradient* currentGradient{0}; KoColor currentFgColor; KoColor currentBgColor; float currentExposure{1.0}; KisFilterConfigurationSP currentGenerator; QWidget* optionWidget{0}; ToolMode m_mode{HOVER_MODE}; bool m_isActive{false}; }; KisTool::KisTool(KoCanvasBase * canvas, const QCursor & cursor) : KoToolBase(canvas) , d(new Private) { d->cursor = cursor; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle())); connect(this, SIGNAL(isActiveChanged()), SLOT(resetCursorStyle())); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); if (!collection->action("toggle_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("toggle_fg_bg", collection); collection->addAction("toggle_fg_bg", toggleFgBg); } if (!collection->action("reset_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("reset_fg_bg", collection); collection->addAction("reset_fg_bg", toggleFgBg); } addAction("toggle_fg_bg", dynamic_cast(collection->action("toggle_fg_bg"))); addAction("reset_fg_bg", dynamic_cast(collection->action("reset_fg_bg"))); } KisTool::~KisTool() { delete d; } void KisTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); resetCursorStyle(); if (!canvas()) return; if (!canvas()->resourceManager()) return; d->currentFgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value(); d->currentBgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::BackgroundColor).value(); if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentPattern)) { d->currentPattern = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPattern).value(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGradient)) { d->currentGradient = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGradient).value(); } KisPaintOpPresetSP preset = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && preset->settings()) { preset->settings()->activate(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::HdrExposure)) { d->currentExposure = static_cast(canvas()->resourceManager()->resource(KisCanvasResourceProvider::HdrExposure).toDouble()); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration)) { d->currentGenerator = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); } connect(action("toggle_fg_bg"), SIGNAL(triggered()), SLOT(slotToggleFgBg()), Qt::UniqueConnection); connect(action("reset_fg_bg"), SIGNAL(triggered()), SLOT(slotResetFgBg()), Qt::UniqueConnection); connect(image(), SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection); d->m_isActive = true; emit isActiveChanged(); } void KisTool::deactivate() { bool result = true; result &= disconnect(image().data(), SIGNAL(sigUndoDuringStrokeRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeCancellationRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeEndRequested()), this, 0); result &= disconnect(action("toggle_fg_bg"), 0, this, 0); result &= disconnect(action("reset_fg_bg"), 0, this, 0); if (!result) { warnKrita << "WARNING: KisTool::deactivate() failed to disconnect" << "some signal connections. Your actions might be executed twice!"; } d->m_isActive = false; emit isActiveChanged(); KoToolBase::deactivate(); } void KisTool::canvasResourceChanged(int key, const QVariant & v) { switch (key) { case(KoCanvasResourceManager::ForegroundColor): d->currentFgColor = v.value(); break; case(KoCanvasResourceManager::BackgroundColor): d->currentBgColor = v.value(); break; case(KisCanvasResourceProvider::CurrentPattern): d->currentPattern = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentGradient): d->currentGradient = static_cast(v.value()); break; case(KisCanvasResourceProvider::HdrExposure): d->currentExposure = static_cast(v.toDouble()); break; case(KisCanvasResourceProvider::CurrentGeneratorConfiguration): d->currentGenerator = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentPaintOpPreset): emit statusTextChanged(v.value()->name()); break; case(KisCanvasResourceProvider::CurrentKritaNode): resetCursorStyle(); break; default: break; // Do nothing }; } void KisTool::updateSettingsViews() { } QPointF KisTool::widgetCenterInWidgetPixels() { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter(); return converter->flakeToWidget(converter->flakeCenterPoint()); } QPointF KisTool::convertDocumentToWidget(const QPointF& pt) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->documentToWidget(pt); } QPointF KisTool::convertToPixelCoord(KoPointerEvent *e) { if (!image()) return e->point; return image()->documentToPixel(e->point); } QPointF KisTool::convertToPixelCoord(const QPointF& pt) { if (!image()) return pt; return image()->documentToPixel(pt); } QPointF KisTool::convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!image()) return e->point; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); return image()->documentToPixel(pos); } QPointF KisTool::convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset) { if (!image()) return pt; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return image()->documentToPixel(pos); } QPoint KisTool::convertToIntPixelCoord(KoPointerEvent *e) { if (!image()) return e->point.toPoint(); return image()->documentToIntPixel(e->point); } QPointF KisTool::viewToPixel(const QPointF &viewCoord) const { if (!image()) return viewCoord; return image()->documentToPixel(canvas()->viewConverter()->viewToDocument(viewCoord)); } QRectF KisTool::convertToPt(const QRectF &rect) { if (!image()) return rect; QRectF r; //We add 1 in the following to the extreme coords because a pixel always has size r.setCoords(int(rect.left()) / image()->xRes(), int(rect.top()) / image()->yRes(), - int(1 + rect.right()) / image()->xRes(), int(1 + rect.bottom()) / image()->yRes()); + int(rect.right()) / image()->xRes(), int( rect.bottom()) / image()->yRes()); return r; } QPointF KisTool::pixelToView(const QPoint &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QPointF KisTool::pixelToView(const QPointF &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QRectF KisTool::pixelToView(const QRectF &pixelRect) const { if (!image()) return pixelRect; QPointF topLeft = pixelToView(pixelRect.topLeft()); QPointF bottomRight = pixelToView(pixelRect.bottomRight()); return QRectF(topLeft, bottomRight); } QPainterPath KisTool::pixelToView(const QPainterPath &pixelPolygon) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPolygon); } QPolygonF KisTool::pixelToView(const QPolygonF &pixelPath) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPath); } void KisTool::updateCanvasPixelRect(const QRectF &pixelRect) { canvas()->updateCanvas(convertToPt(pixelRect)); } void KisTool::updateCanvasViewRect(const QRectF &viewRect) { canvas()->updateCanvas(canvas()->viewConverter()->viewToDocument(viewRect)); } KisImageWSP KisTool::image() const { // For now, krita tools only work in krita, not for a krita shape. Krita shapes are for 2.1 KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (kisCanvas) { return kisCanvas->currentImage(); } return 0; } QCursor KisTool::cursor() const { return d->cursor; } void KisTool::notifyModified() const { if (image()) { image()->setModified(); } } KoPattern * KisTool::currentPattern() { return d->currentPattern; } KoAbstractGradient * KisTool::currentGradient() { return d->currentGradient; } KisPaintOpPresetSP KisTool::currentPaintOpPreset() { return canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); } KisNodeSP KisTool::currentNode() const { KisNodeSP node = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); return node; } KisNodeList KisTool::selectedNodes() const { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->nodeManager()->selectedNodes(); } KoColor KisTool::currentFgColor() { return d->currentFgColor; } KoColor KisTool::currentBgColor() { return d->currentBgColor; } KisImageWSP KisTool::currentImage() { return image(); } KisFilterConfigurationSP KisTool::currentGenerator() { return d->currentGenerator; } void KisTool::setMode(ToolMode mode) { d->m_mode = mode; } KisTool::ToolMode KisTool::mode() const { return d->m_mode; } void KisTool::setCursor(const QCursor &cursor) { d->cursor = cursor; } KisTool::AlternateAction KisTool::actionToAlternateAction(ToolAction action) { KIS_ASSERT_RECOVER_RETURN_VALUE(action != Primary, Secondary); return (AlternateAction)action; } void KisTool::activatePrimaryAction() { resetCursorStyle(); } void KisTool::deactivatePrimaryAction() { resetCursorStyle(); } void KisTool::beginPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::beginPrimaryDoubleClickAction(KoPointerEvent *event) { beginPrimaryAction(event); } void KisTool::continuePrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } bool KisTool::primaryActionSupportsHiResEvents() const { return false; } void KisTool::activateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::deactivateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action) { beginAlternateAction(event, action); } void KisTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseTripleClickEvent(KoPointerEvent *event) { mouseDoubleClickEvent(event); } void KisTool::mousePressEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseMoveEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::deleteSelection() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); if (!blockUntilOperationsFinished()) { return; } if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) { KoToolBase::deleteSelection(); } } void KisTool::setupPaintAction(KisRecordedPaintAction* action) { action->setPaintColor(currentFgColor()); action->setBackgroundColor(currentBgColor()); } QWidget* KisTool::createOptionWidget() { d->optionWidget = new QLabel(i18n("No options")); d->optionWidget->setObjectName("SpecialSpacer"); return d->optionWidget; } #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #define PROGRAM_VERTEX_ATTRIBUTE 0 void KisTool::paintToolOutline(QPainter* painter, const QPainterPath &path) { KisOpenGLCanvas2 *canvasWidget = dynamic_cast(canvas()->canvasWidget()); if (canvasWidget) { painter->beginNativePainting(); canvasWidget->paintToolOutline(path); painter->endNativePainting(); } else { painter->setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter->setPen(QColor(128, 255, 128)); painter->drawPath(path); } } void KisTool::resetCursorStyle() { useCursor(d->cursor); } bool KisTool::overrideCursorIfNotEditable() { // override cursor for canvas iff this tool is active // and we can't paint on the active layer if (isActive()) { KisNodeSP node = currentNode(); if (node && !node->isEditable()) { canvas()->setCursor(Qt::ForbiddenCursor); return true; } } return false; } bool KisTool::blockUntilOperationsFinished() { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->blockUntilOperationsFinished(image()); } void KisTool::blockUntilOperationsFinishedForced() { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->blockUntilOperationsFinishedForced(image()); } bool KisTool::isActive() const { return d->m_isActive; } void KisTool::slotToggleFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); KoColor newFg = resourceManager->backgroundColor(); KoColor newBg = resourceManager->foregroundColor(); /** * NOTE: Some of color selectors do not differentiate foreground * and background colors, so if one wants them to end up * being set up to foreground color, it should be set the * last. */ resourceManager->setBackgroundColor(newBg); resourceManager->setForegroundColor(newFg); } void KisTool::slotResetFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); // see a comment in slotToggleFgBg() resourceManager->setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8())); resourceManager->setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8())); } bool KisTool::nodeEditable() { KisNodeSP node = currentNode(); if (!node) { return false; } bool nodeEditable = node->isEditable(); if (!nodeEditable) { KisCanvas2 * kiscanvas = static_cast(canvas()); QString message; if (!node->visible() && node->userLocked()) { message = i18n("Layer is locked and invisible."); } else if (node->userLocked()) { message = i18n("Layer is locked."); } else if(!node->visible()) { message = i18n("Layer is invisible."); } else { message = i18n("Group not editable."); } kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked")); } return nodeEditable; } bool KisTool::selectionEditable() { KisCanvas2 * kisCanvas = static_cast(canvas()); KisViewManager * view = kisCanvas->viewManager(); bool editable = view->selectionEditable(); if (!editable) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage(i18n("Local selection is locked."), KisIconUtils::loadIcon("object-locked")); } return editable; } void KisTool::listenToModifiers(bool listen) { Q_UNUSED(listen); } bool KisTool::listeningToModifiers() { return false; } diff --git a/libs/ui/tool/kis_tool.h b/libs/ui/tool/kis_tool.h index bec8e7abe8..a5884f39a4 100644 --- a/libs/ui/tool/kis_tool.h +++ b/libs/ui/tool/kis_tool.h @@ -1,325 +1,324 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_H_ #define KIS_TOOL_H_ #include #include #include #include #include #include #include - #ifdef __GNUC__ #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come to" << __func__ << "while being mode" << _mode << "!" #else #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come while being mode" << _mode << "!" #endif #define CHECK_MODE_SANITY_OR_RETURN(_mode) if (mode() != _mode) { WARN_WRONG_MODE(mode()); return; } class KoCanvasBase; class KoPattern; class KoAbstractGradient; class KisFilterConfiguration; class QPainter; class QPainterPath; class QPolygonF; class KisRecordedPaintAction; /// Definitions of the toolgroups of Krita static const QString TOOL_TYPE_SHAPE = "0 Krita/Shape"; // Geometric shapes like ellipses and lines static const QString TOOL_TYPE_TRANSFORM = "2 Krita/Transform"; // Tools that transform the layer; static const QString TOOL_TYPE_FILL = "3 Krita/Fill"; // Tools that fill parts of the canvas static const QString TOOL_TYPE_VIEW = "4 Krita/View"; // Tools that affect the canvas: pan, zoom, etc. static const QString TOOL_TYPE_SELECTION = "5 Krita/Select"; // Tools that select pixels //activation id for Krita tools, Krita tools are always active and handle locked and invisible layers by themself static const QString KRITA_TOOL_ACTIVATION_ID = "flake/always"; class KRITAUI_EXPORT KisTool : public KoToolBase { Q_OBJECT Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged) public: enum { FLAG_USES_CUSTOM_PRESET=0x01, FLAG_USES_CUSTOM_COMPOSITEOP=0x02, FLAG_USES_CUSTOM_SIZE=0x04 }; KisTool(KoCanvasBase * canvas, const QCursor & cursor); ~KisTool() override; virtual int flags() const { return 0; } void deleteSelection() override; // KoToolBase Implementation. public: /** * Called by KisToolProxy when the primary action of the tool is * going to be started now, that is when all the modifiers are * pressed and the only thing left is just to press the mouse * button. On coming of this callback the tool is supposed to * prepare the cursor and/or the outline to show the user shat is * going to happen next */ virtual void activatePrimaryAction(); /** * Called by KisToolProxy when the primary is no longer possible * to be started now, e.g. when its modifiers and released. The * tool is supposed revert all the preparetions it has doen in * activatePrimaryAction(). */ virtual void deactivatePrimaryAction(); /** * Called by KisToolProxy when a primary action for the tool is * started. The \p event stores the original event that * started the stroke. The \p event is _accepted_ by default. If * the tool decides to ignore this particular action (e.g. when * the node is not editable), it should call event->ignore(). Then * no further continuePrimaryAction() or endPrimaryAction() will * be called until the next user action. */ virtual void beginPrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is in progress * of pointer movement. If the tool has ignored the event in * beginPrimaryAction(), this method will not be called. */ virtual void continuePrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is being * finished, that is while mouseRelease or tabletRelease event. * If the tool has ignored the event in beginPrimaryAction(), this * method will not be called. */ virtual void endPrimaryAction(KoPointerEvent *event); /** * The same as beginPrimaryAction(), but called when the stroke is * started by a double-click * * \see beginPrimaryAction() */ virtual void beginPrimaryDoubleClickAction(KoPointerEvent *event); /** * Returns true if the tool can handle (and wants to handle) a * very tight flow of input events from the tablet */ virtual bool primaryActionSupportsHiResEvents() const; enum ToolAction { Primary, AlternateChangeSize, AlternatePickFgNode, AlternatePickBgNode, AlternatePickFgImage, AlternatePickBgImage, AlternateSecondary, AlternateThird, AlternateFourth, AlternateFifth, Alternate_NONE = 10000 }; // Technically users are allowed to configure this, but nobody ever would do that. // So these can basically be thought of as aliases to ctrl+click, etc. enum AlternateAction { ChangeSize = AlternateChangeSize, // Default: Shift+Left click PickFgNode = AlternatePickFgNode, // Default: Ctrl+Alt+Left click PickBgNode = AlternatePickBgNode, // Default: Ctrl+Alt+Right click PickFgImage = AlternatePickFgImage, // Default: Ctrl+Left click PickBgImage = AlternatePickBgImage, // Default: Ctrl+Right click Secondary = AlternateSecondary, Third = AlternateThird, Fourth = AlternateFourth, Fifth = AlternateFifth, NONE = 10000 }; static AlternateAction actionToAlternateAction(ToolAction action); virtual void activateAlternateAction(AlternateAction action); virtual void deactivateAlternateAction(AlternateAction action); virtual void beginAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void continueAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void endAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action); void mousePressEvent(KoPointerEvent *event) override; void mouseDoubleClickEvent(KoPointerEvent *event) override; void mouseTripleClickEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; bool isActive() const; public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; void canvasResourceChanged(int key, const QVariant & res) override; // Implement this slot in case there are any widgets or properties which need // to be updated after certain operations, to reflect the inner state correctly. // At the moment this is used for smoothing options in the freehand brush, but // this will likely be expanded. virtual void updateSettingsViews(); Q_SIGNALS: void isActiveChanged(); protected: // conversion methods are also needed by the paint information builder friend class KisToolPaintingInformationBuilder; /// Convert from native (postscript points) to image pixel /// coordinates. QPointF convertToPixelCoord(KoPointerEvent *e); QPointF convertToPixelCoord(const QPointF& pt); QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset = QPointF(), bool useModifiers = true); QPointF convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset = QPointF()); protected: QPointF widgetCenterInWidgetPixels(); QPointF convertDocumentToWidget(const QPointF& pt); /// Convert from native (postscript points) to integer image pixel /// coordinates. This truncates the floating point components and /// should be used in preference to QPointF::toPoint(), which rounds, /// to ensure the cursor acts on the pixel it is visually over. QPoint convertToIntPixelCoord(KoPointerEvent *e); QRectF convertToPt(const QRectF &rect); QPointF viewToPixel(const QPointF &viewCoord) const; /// Convert an integer pixel coordinate into a view coordinate. /// The view coordinate is at the centre of the pixel. QPointF pixelToView(const QPoint &pixelCoord) const; /// Convert a floating point pixel coordinate into a view coordinate. QPointF pixelToView(const QPointF &pixelCoord) const; /// Convert a pixel rectangle into a view rectangle. QRectF pixelToView(const QRectF &pixelRect) const; /// Convert a pixel path into a view path QPainterPath pixelToView(const QPainterPath &pixelPath) const; /// Convert a pixel polygon into a view path QPolygonF pixelToView(const QPolygonF &pixelPolygon) const; /// Update the canvas for the given rectangle in image pixel coordinates. void updateCanvasPixelRect(const QRectF &pixelRect); /// Update the canvas for the given rectangle in view coordinates. void updateCanvasViewRect(const QRectF &viewRect); QWidget* createOptionWidget() override; /** * To determine whether this tool will change its behavior when * modifier keys are pressed */ virtual bool listeningToModifiers(); /** * Request that this tool no longer listen to modifier keys * (Responding to the request is optional) */ virtual void listenToModifiers(bool listen); protected: KisImageWSP image() const; QCursor cursor() const; /// Call this to set the document modified void notifyModified() const; KisImageWSP currentImage(); KoPattern* currentPattern(); KoAbstractGradient *currentGradient(); KisNodeSP currentNode() const; KisNodeList selectedNodes() const; KoColor currentFgColor(); KoColor currentBgColor(); KisPaintOpPresetSP currentPaintOpPreset(); KisFilterConfigurationSP currentGenerator(); virtual void setupPaintAction(KisRecordedPaintAction* action); /// paint the path which is in view coordinates, default paint mode is XOR_MODE, BW_MODE is also possible /// never apply transformations to the painter, they would be useless, if drawing in OpenGL mode. The coordinates in the path should be in view coordinates. void paintToolOutline(QPainter * painter, const QPainterPath &path); /// Checks checks if the current node is editable bool nodeEditable(); /// Checks checks if the selection is editable, only applies to local selection as global selection is always editable bool selectionEditable(); /// Override the cursor appropriately if current node is not editable bool overrideCursorIfNotEditable(); bool blockUntilOperationsFinished(); void blockUntilOperationsFinishedForced(); protected: enum ToolMode { HOVER_MODE, PAINT_MODE, SECONDARY_PAINT_MODE, MIRROR_AXIS_SETUP_MODE, GESTURE_MODE, PAN_MODE, OTHER // not used now }; virtual void setMode(ToolMode mode); virtual ToolMode mode() const; void setCursor(const QCursor &cursor); protected Q_SLOTS: /** * Called whenever the configuration settings change. */ virtual void resetCursorStyle(); private Q_SLOTS: void slotToggleFgBg(); void slotResetFgBg(); private: struct Private; Private* const d; }; #endif // KIS_TOOL_H_ diff --git a/libs/ui/widgets/kis_paintop_presets_popup.cpp b/libs/ui/widgets/kis_paintop_presets_popup.cpp index 60df1e9e01..1e6d017295 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.cpp +++ b/libs/ui/widgets/kis_paintop_presets_popup.cpp @@ -1,584 +1,762 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "widgets/kis_paintop_presets_popup.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_resource_server_provider.h" #include "kis_lod_availability_widget.h" #include "kis_signal_auto_connection.h" - +#include // ones from brush engine selector #include struct KisPaintOpPresetsPopup::Private { public: Ui_WdgPaintOpSettings uiWdgPaintOpPresetSettings; QGridLayout *layout; KisPaintOpConfigWidget *settingsWidget; QFont smallFont; KisCanvasResourceProvider *resourceProvider; + KisFavoriteResourceManager *favoriteResManager; + bool detached; bool ignoreHideEvents; QSize minimumSettingsWidgetSize; QRect detachedGeometry; KisSignalAutoConnectionsStore widgetConnections; }; -KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, QWidget * parent) +KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, + KisFavoriteResourceManager* favoriteResourceManager, + KisPresetSaveWidget* savePresetWidget, + QWidget * parent) : QWidget(parent) , m_d(new Private()) { setObjectName("KisPaintOpPresetsPopup"); setFont(KoDockRegistry::dockFont()); current_paintOpId = ""; m_d->resourceProvider = resourceProvider; + m_d->favoriteResManager = favoriteResourceManager; m_d->uiWdgPaintOpPresetSettings.setupUi(this); m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer); m_d->layout->setSizeConstraint(QLayout::SetFixedSize); m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white); m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200)); + m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); + m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); - m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); + m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setIcon(KisIconUtils::loadIcon("updateColorize")); // refresh icon + m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setIcon(KisIconUtils::loadIcon("dirty-preset")); // edit icon + + m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setIcon(KisIconUtils::loadIcon("warning")); + m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setToolTip(i18n("The settings for this preset have changed from their default.")); + + + m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setToolTip(i18n("Reload the brush preset")); + m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setToolTip(i18n("Rename the brush preset")); + + + // overwrite existing preset and saving a new preset use the same dialog + saveDialog = savePresetWidget; + saveDialog->scratchPadSetup(resourceProvider); + saveDialog->setFavoriteResourceManager(m_d->favoriteResManager); // this is needed when saving the preset + saveDialog->hide(); + + // the area on the brush editor for renaming the brush. make sure edit fields are hidden by default + toggleBrushRenameUIActive(false); // DETAIL and THUMBNAIL view changer QMenu* menu = new QMenu(this); menu->setStyleSheet("margin: 6px"); menu->addSection(i18n("Display")); QActionGroup *actionGroup = new QActionGroup(this); KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig().presetChooserViewMode(); QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotThumbnailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::THUMBNAIL); action->setActionGroup(actionGroup); action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotDetailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::DETAIL); action->setActionGroup(actionGroup); // add horizontal slider for the icon size QSlider* iconSizeSlider = new QSlider(this); iconSizeSlider->setOrientation(Qt::Horizontal); iconSizeSlider->setRange(30, 80); iconSizeSlider->setValue(m_d->uiWdgPaintOpPresetSettings.presetWidget->iconSize()); iconSizeSlider->setMinimumHeight(20); iconSizeSlider->setMinimumWidth(40); iconSizeSlider->setTickInterval(10); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(iconSizeSlider); menu->addSection(i18n("Icon Size")); menu->addAction(sliderAction); // configure the button and assign menu m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setMenu(menu); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setPopupMode(QToolButton::InstantPopup); // show/hide buttons KisConfig cfg; m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible()); m_d->uiWdgPaintOpPresetSettings.showEditorButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showEditorButton->setChecked(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setText(i18n("Presets")); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false); // use a config to load/save this state - slotSwitchShowPresets(false); // hide presets by default + slotSwitchShowPresets(false); // hide presets by default // Connections + connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()), + m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage())); + + connect(saveDialog, SIGNAL(resourceSelected(KoResource*)), this, SLOT(resourceSelected(KoResource*))); + + connect (m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton, SIGNAL(clicked(bool)), + this, SLOT(slotRenameBrushActivated())); + + connect (m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton, SIGNAL(clicked(bool)), + this, SLOT(slotRenameBrushDeactivated())); + + connect(m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton, SIGNAL(clicked(bool)), + this, SLOT(slotSaveRenameCurrentBrush())); + + connect(m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField, SIGNAL(returnPressed()), + SLOT(slotSaveRenameCurrentBrush())); connect(iconSizeSlider, SIGNAL(sliderMoved(int)), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSetIconSize(int))); connect(iconSizeSlider, SIGNAL(sliderReleased()), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSaveIconSize())); connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchScratchpad(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showEditorButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowEditor(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault())); connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillLayer())); connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient())); connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground())); - connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()), - m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage())); m_d->settingsWidget = 0; setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); - connect(m_d->uiWdgPaintOpPresetSettings.bnSave, SIGNAL(clicked()), - this, SIGNAL(savePresetClicked())); + connect(m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton, SIGNAL(clicked()), + this, SLOT(slotSaveBrushPreset())); - connect(m_d->uiWdgPaintOpPresetSettings.reload, SIGNAL(clicked()), + connect(m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton, SIGNAL(clicked()), + this, SLOT(slotSaveNewBrushPreset())); + + connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), this, SIGNAL(reloadPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), this, SIGNAL(defaultPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(dirtyPresetToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushSizeToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)), - this, SIGNAL(eraserBrushOpacityToggled(bool))); - - connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), - m_d->uiWdgPaintOpPresetSettings.txtPreset, SLOT(clear())); - - connect(m_d->uiWdgPaintOpPresetSettings.txtPreset, SIGNAL(textChanged(QString)), - SLOT(slotWatchPresetNameLineEdit())); + this, SIGNAL(eraserBrushOpacityToggled(bool))); // preset widget connections connect(m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(signalResourceSelected(KoResource*))); - connect(m_d->uiWdgPaintOpPresetSettings.bnSave, SIGNAL(clicked()), + connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); - connect(m_d->uiWdgPaintOpPresetSettings.reload, SIGNAL(clicked()), - m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); + + + connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), SLOT(slotUpdatePresetSettings())); + m_d->detached = false; m_d->ignoreHideEvents = false; m_d->minimumSettingsWidgetSize = QSize(0, 0); m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(cfg.scratchpadVisible()); m_d->detachedGeometry = QRect(100, 100, 0, 0); m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets()); m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize()); m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity()); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setCanvasResourceManager(resourceProvider->resourceManager()); connect(resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(slotResourceChanged(int, QVariant))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodAvailability(bool)), SLOT(slotLodAvailabilityChanged(bool))); slotResourceChanged(KisCanvasResourceProvider::LodAvailability, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodAvailability)); connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePaintOpFilter())); } +void KisPaintOpPresetsPopup::slotRenameBrushActivated() +{ + toggleBrushRenameUIActive(true); +} + +void KisPaintOpPresetsPopup::slotRenameBrushDeactivated() +{ + toggleBrushRenameUIActive(false); +} + +void KisPaintOpPresetsPopup::toggleBrushRenameUIActive(bool isRenaming) +{ + // This function doesn't really do anything except get the UI in a state to rename a brush preset + + m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(!isRenaming); + m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setVisible(!isRenaming); + m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(!isRenaming); + + m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setVisible(isRenaming); + m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton->setVisible(isRenaming); + m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton->setVisible(isRenaming); + + m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(!isRenaming); + m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setEnabled(!isRenaming); +} + +void KisPaintOpPresetsPopup::slotSaveRenameCurrentBrush() +{ + // if you are renaming a brush, that is different than updating the settings + // make sure we are in a clean state before renaming. This logic might change, + // but that is what we are going with for now + emit reloadPresetClicked(); + + + m_d->favoriteResManager->setBlockUpdates(true); + + // get a reference to the existing (and new) file name and path that we are working with + KisPaintOpPresetSP curPreset = m_d->resourceProvider->currentPreset(); + + if (!curPreset) + return; + + KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); + QString saveLocation = rServer->saveLocation(); + + QString originalPresetName = curPreset->name(); + QString renamedPresetName = m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->text(); + QString originalPresetPathAndFile = saveLocation + originalPresetName + curPreset->defaultFileExtension(); + QString renamedPresetPathAndFile = saveLocation + renamedPresetName + curPreset->defaultFileExtension(); + + + // create a new brush preset with the name specified and add to resource provider + KisPaintOpPresetSP newPreset = curPreset->clone(); + newPreset->setFilename(renamedPresetPathAndFile); // this also contains the path + newPreset->setName(renamedPresetName); + newPreset->setImage(curPreset->image()); // use existing thumbnail (might not need to do this) + newPreset->setPresetDirty(false); + newPreset->setValid(true); + rServer->addResource(newPreset); + + resourceSelected(newPreset.data()); // refresh and select our freshly renamed resource + + + // Now blacklist the original file + if (rServer->resourceByName(originalPresetName)) { + rServer->removeResourceAndBlacklist(curPreset); + } + + m_d->favoriteResManager->setBlockUpdates(false); + + toggleBrushRenameUIActive(false); // this returns the UI to its original state after saving + + slotUpdatePresetSettings(); // update visibility of dirty preset and icon +} + + void KisPaintOpPresetsPopup::slotResourceChanged(int key, const QVariant &value) { if (key == KisCanvasResourceProvider::LodAvailability) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodAvailability(value.toBool()); } } void KisPaintOpPresetsPopup::slotLodAvailabilityChanged(bool value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodAvailability, QVariant(value)); } KisPaintOpPresetsPopup::~KisPaintOpPresetsPopup() { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->settingsWidget->hide(); m_d->settingsWidget->setParent(0); m_d->settingsWidget = 0; } delete m_d; } void KisPaintOpPresetsPopup::setPaintOpSettingsWidget(QWidget * widget) { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry(); } m_d->layout->update(); updateGeometry(); m_d->widgetConnections.clear(); m_d->settingsWidget = 0; if (widget) { m_d->settingsWidget = dynamic_cast(widget); KIS_ASSERT_RECOVER_RETURN(m_d->settingsWidget); if (m_d->settingsWidget->supportScratchBox()) { showScratchPad(); } else { hideScratchPad(); } m_d->widgetConnections.addConnection(m_d->settingsWidget, SIGNAL(sigConfigurationItemChanged()), this, SLOT(slotUpdateLodAvailability())); widget->setFont(m_d->smallFont); QSize hint = widget->sizeHint(); m_d->minimumSettingsWidgetSize = QSize(qMax(hint.width(), m_d->minimumSettingsWidgetSize.width()), qMax(hint.height(), m_d->minimumSettingsWidgetSize.height())); widget->setMinimumSize(m_d->minimumSettingsWidgetSize); m_d->layout->addWidget(widget); + // hook up connections that will monitor if our preset is dirty or not. Show a notification if it is + if (m_d->resourceProvider && m_d->resourceProvider->currentPreset() ) { + + KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset(); + m_d->widgetConnections.addConnection(preset->updateProxy(), SIGNAL(sigSettingsChanged()), + this, SLOT(slotUpdatePresetSettings())); + + } + m_d->layout->update(); widget->show(); } slotUpdateLodAvailability(); } void KisPaintOpPresetsPopup::slotUpdateLodAvailability() { if (!m_d->settingsWidget) return; KisPaintopLodLimitations l = m_d->settingsWidget->lodLimitations(); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLimitations(l); } -void KisPaintOpPresetsPopup::slotWatchPresetNameLineEdit() -{ - QString text = m_d->uiWdgPaintOpPresetSettings.txtPreset->text(); - KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); - bool overwrite = rServer->resourceByName(text) != 0; +void KisPaintOpPresetsPopup::slotUpdatePresetSettings() +{ + if (!m_d->resourceProvider) { + return; + } + if (!m_d->resourceProvider->currentPreset()) { + return; + } - KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset(); - bool btnSaveAvailable = preset->valid() && - (preset->isPresetDirty() | !overwrite); + bool isPresetDirty = m_d->resourceProvider->currentPreset()->isPresetDirty(); - QString btnText = overwrite ? i18n("Overwrite Preset") : i18n("Save to Presets"); + // don't need to reload or overwrite a clean preset + m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setVisible(isPresetDirty); + m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(isPresetDirty); + m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(isPresetDirty); - m_d->uiWdgPaintOpPresetSettings.bnSave->setText(btnText); - m_d->uiWdgPaintOpPresetSettings.bnSave->setEnabled(btnSaveAvailable); - m_d->uiWdgPaintOpPresetSettings.reload->setVisible(true); - m_d->uiWdgPaintOpPresetSettings.reload->setEnabled(btnSaveAvailable && overwrite); - QFont font = m_d->uiWdgPaintOpPresetSettings.txtPreset->font(); - font.setItalic(btnSaveAvailable); - m_d->uiWdgPaintOpPresetSettings.txtPreset->setFont(font); } -QString KisPaintOpPresetsPopup::getPresetName() const -{ - return m_d->uiWdgPaintOpPresetSettings.txtPreset->text(); -} QImage KisPaintOpPresetsPopup::cutOutOverlay() { return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay(); } void KisPaintOpPresetsPopup::contextMenuEvent(QContextMenuEvent *e) { Q_UNUSED(e); } void KisPaintOpPresetsPopup::switchDetached(bool show) { if (parentWidget()) { m_d->detached = !m_d->detached; if (m_d->detached) { m_d->ignoreHideEvents = true; if (show) { parentWidget()->show(); } m_d->ignoreHideEvents = false; } else { KisConfig cfg; parentWidget()->hide(); } KisConfig cfg; cfg.setPaintopPopupDetached(m_d->detached); } } void KisPaintOpPresetsPopup::hideScratchPad() { m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(false); } void KisPaintOpPresetsPopup::showScratchPad() { m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(true); } void KisPaintOpPresetsPopup::resourceSelected(KoResource* resource) { + // this gets called every time the brush editor window is opened + // TODO: this gets called multiple times whenever the preset is changed in the presets area + // the connections probably need to be thought about with this a bit more to keep things in sync + m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(resource); - m_d->uiWdgPaintOpPresetSettings.txtPreset->setText(resource->name()); - slotWatchPresetNameLineEdit(); // find the display name of the brush engine and append it to the selected preset display QString currentBrushEngineName; for(int i=0; i < sortedBrushEnginesList.length(); i++) { if (sortedBrushEnginesList.at(i).id == currentPaintOpId() ) { currentBrushEngineName = sortedBrushEnginesList.at(i).name; } } QString selectedBrush = resource->name().append(" (").append(currentBrushEngineName).append(" ").append("Engine").append(")"); m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(selectedBrush); + m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setText(resource->name()); + + + // get the preset image and pop it into the thumbnail area on the top of the brush editor + QGraphicsScene * thumbScene = new QGraphicsScene(this); + thumbScene->addPixmap(QPixmap::fromImage(resource->image().scaled(30, 30))); + thumbScene->setSceneRect(0, 0, 30, 30); // 30 x 30 image for thumb. this is also set in the UI + m_d->uiWdgPaintOpPresetSettings.presetThumbnailicon->setScene(thumbScene); + + toggleBrushRenameUIActive(false); // reset the UI state of renaming a brush if we are changing brush presets + slotUpdatePresetSettings(); // check to see if the dirty preset icon needs to be shown } bool variantLessThan(const KisPaintOpInfo v1, const KisPaintOpInfo v2) { return v1.priority < v2.priority; } void KisPaintOpPresetsPopup::setPaintOpList(const QList< KisPaintOpFactory* >& list) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->clear(); // reset combobox list just in case // create a new list so we can sort it and populate the brush engine combo box sortedBrushEnginesList.clear(); // just in case this function is called again, don't keep adding to the list for(int i=0; i < list.length(); i++) { QString fileName = KoResourcePaths::findResource("kis_images", list.at(i)->pixmap()); QPixmap pixmap(fileName); if(pixmap.isNull()){ pixmap = QPixmap(22,22); pixmap.fill(); } KisPaintOpInfo paintOpInfo; paintOpInfo.id = list.at(i)->id(); paintOpInfo.name = list.at(i)->name(); paintOpInfo.icon = pixmap; paintOpInfo.priority = list.at(i)->priority(); sortedBrushEnginesList.append(paintOpInfo); } std::stable_sort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan ); // add an "All" option at the front to show all presets QPixmap emptyPixmap = QPixmap(22,22); emptyPixmap.fill(palette().color(QPalette::Background)); sortedBrushEnginesList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), emptyPixmap, 0 )); // fill the list into the brush combo box for (int m = 0; m < sortedBrushEnginesList.length(); m++) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->addItem(sortedBrushEnginesList[m].icon, sortedBrushEnginesList[m].name, QVariant(sortedBrushEnginesList[m].id)); } } void KisPaintOpPresetsPopup::setCurrentPaintOpId(const QString& paintOpId) { current_paintOpId = paintOpId; } QString KisPaintOpPresetsPopup::currentPaintOpId() { return current_paintOpId; } void KisPaintOpPresetsPopup::setPresetImage(const QImage& image) { m_d->uiWdgPaintOpPresetSettings.scratchPad->setPresetImage(image); + saveDialog->brushPresetThumbnailWidget->setPresetImage(image); } void KisPaintOpPresetsPopup::hideEvent(QHideEvent *event) { if (m_d->ignoreHideEvents) { return; } if (m_d->detached) { m_d->detachedGeometry = window()->geometry(); } QWidget::hideEvent(event); } void KisPaintOpPresetsPopup::showEvent(QShowEvent *) { if (m_d->detached) { window()->setGeometry(m_d->detachedGeometry); } emit brushEditorShown(); } void KisPaintOpPresetsPopup::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); emit sizeChanged(); } bool KisPaintOpPresetsPopup::detached() const { return m_d->detached; } void KisPaintOpPresetsPopup::slotSwitchScratchpad(bool visible) { m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(visible); KisConfig cfg; cfg.setScratchpadVisible(visible); + calculateShowingTopArea(); } +void KisPaintOpPresetsPopup::calculateShowingTopArea() { + // if the scratchpad is the only area visible, we should hide the currently selected brush and settings + // so the top area doesn't + + bool shouldDisplayTopBar = true; + + if (m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->isChecked() && !m_d->uiWdgPaintOpPresetSettings.showPresetsButton->isChecked() && + !m_d->uiWdgPaintOpPresetSettings.showEditorButton->isChecked() ) { + shouldDisplayTopBar = false; + } + + m_d->uiWdgPaintOpPresetSettings.presetThumbnailicon->setVisible(shouldDisplayTopBar); + m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setVisible(shouldDisplayTopBar); + m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(shouldDisplayTopBar); + + // always hide these since they are part of the brush renaming field and can make things get in a weird state + m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setVisible(false); + m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton->setVisible(false); + m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton->setVisible(false); + + slotUpdatePresetSettings(); // find out if the preset is dirty or not and update visibility + +} + + void KisPaintOpPresetsPopup::slotSwitchShowEditor(bool visible) { m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible); + calculateShowingTopArea(); } void KisPaintOpPresetsPopup::slotSwitchShowPresets(bool visible) { m_d->uiWdgPaintOpPresetSettings.presetsContainer->setVisible(visible); + calculateShowingTopArea() ; } void KisPaintOpPresetsPopup::slotUpdatePaintOpFilter() { QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data QString filterPaintOpId = userData.toString(); if (filterPaintOpId == "all_options") { filterPaintOpId = ""; } m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(filterPaintOpId); } +void KisPaintOpPresetsPopup::slotSaveBrushPreset() { + // here we are assuming that people want to keep their existing preset icon. We will just update the + // settings and save a new copy with the same name. + // there is a dialog with save options, but we don't need to show it in this situation + + saveDialog->isSavingNewBrush(false); // this mostly just makes sure we keep the existing brush preset name when saving + saveDialog->loadExistingThumbnail(); // This makes sure we use the existing preset icon when updating the existing brush preset + saveDialog->savePreset(); + + // refresh the view settings so the brush doesn't appear dirty + slotUpdatePresetSettings(); +} + +void KisPaintOpPresetsPopup::slotSaveNewBrushPreset() { + saveDialog->isSavingNewBrush(true); + saveDialog->saveScratchPadThumbnailArea(m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay()); + saveDialog->showDialog(); +} + + void KisPaintOpPresetsPopup::updateViewSettings() { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->updateViewSettings(); } void KisPaintOpPresetsPopup::currentPresetChanged(KisPaintOpPresetSP preset) -{ +{ m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(preset.data()); setCurrentPaintOpId(preset->paintOp().id()); } void KisPaintOpPresetsPopup::updateThemedIcons() { m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); - m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); } diff --git a/libs/ui/widgets/kis_paintop_presets_popup.h b/libs/ui/widgets/kis_paintop_presets_popup.h index 98078c2e9a..784753c8e0 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.h +++ b/libs/ui/widgets/kis_paintop_presets_popup.h @@ -1,125 +1,138 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_PRESETS_POPUP_H #define KIS_PAINTOP_PRESETS_POPUP_H #include #include #include #include #include #include "../kis_paint_ops_model.h" +#include +#include "widgets/kis_paintop_presets_popup.h" +#include "kis_favorite_resource_manager.h" class QString; class KisCanvasResourceProvider; class KoResource; /** * Popup widget for presets with built-in functionality * for adding and removing presets. */ class KisPaintOpPresetsPopup : public QWidget { Q_OBJECT public: - KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, QWidget * parent = 0); + KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, + KisFavoriteResourceManager* favoriteResourceManager, + KisPresetSaveWidget* savePresetWidget, + QWidget * parent = 0); ~KisPaintOpPresetsPopup() override; void setPaintOpSettingsWidget(QWidget * widget); - /** - * @return the name entered in the preset name lineedit - */ - QString getPresetName() const; ///Image for preset preview ///@return image cut out from the scratchpad QImage cutOutOverlay(); void setPaintOpList(const QList& list); void setCurrentPaintOpId(const QString & paintOpId); /// returns the internal ID for the paint op (brush engine) QString currentPaintOpId(); - + ///fill the cutoutOverlay rect with the cotent of an image, used to get the image back when selecting a preset ///@param image image that will be used, should be image of an existing preset resource void setPresetImage(const QImage& image); void resizeEvent(QResizeEvent* ) override; bool detached() const; void updateViewSettings(); void currentPresetChanged(KisPaintOpPresetSP preset); + KisPresetSaveWidget * saveDialog; + protected: void contextMenuEvent(QContextMenuEvent *) override; void hideEvent(QHideEvent *) override; void showEvent(QShowEvent *) override; public Q_SLOTS: - void slotWatchPresetNameLineEdit(); void switchDetached(bool show = true); void hideScratchPad(); void showScratchPad(); void resourceSelected(KoResource* resource); void updateThemedIcons(); + + void slotUpdatePresetSettings(); void slotUpdateLodAvailability(); + void slotRenameBrushActivated(); + void slotRenameBrushDeactivated(); + void slotSaveRenameCurrentBrush(); Q_SIGNALS: void savePresetClicked(); + void saveBrushPreset(); void defaultPresetClicked(); void paintopActivated(const QString& presetName); void signalResourceSelected(KoResource* resource); void reloadPresetClicked(); void dirtyPresetToggled(bool value); void eraserBrushSizeToggled(bool value); void eraserBrushOpacityToggled(bool value); void sizeChanged(); void brushEditorShown(); private Q_SLOTS: void slotSwitchScratchpad(bool visible); void slotResourceChanged(int key, const QVariant &value); void slotLodAvailabilityChanged(bool value); void slotSwitchShowEditor(bool visible); void slotUpdatePaintOpFilter(); void slotSwitchShowPresets(bool visible); + void slotSaveBrushPreset(); + void slotSaveNewBrushPreset(); private: struct Private; Private * const m_d; QString current_paintOpId; QList sortedBrushEnginesList; + void toggleBrushRenameUIActive(bool isRenaming); + void calculateShowingTopArea(); }; #endif diff --git a/libs/ui/widgets/kis_paintop_presets_save.cpp b/libs/ui/widgets/kis_paintop_presets_save.cpp new file mode 100644 index 0000000000..9f6e807ec4 --- /dev/null +++ b/libs/ui/widgets/kis_paintop_presets_save.cpp @@ -0,0 +1,228 @@ +/* This file is part of the KDE project + * Copyright (C) 2017 Scott Petrovic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "widgets/kis_paintop_presets_save.h" +#include +#include +#include + + +#include +#include "KisImportExportManager.h" +#include "QDesktopServices" +#include "kis_resource_server_provider.h" + + +KisPresetSaveWidget::KisPresetSaveWidget(QWidget * parent) + : KisPaintOpPresetSaveDialog(parent) +{ + // this is setting the area we will "capture" for saving the brush preset. It can potentially be a different + // area that the entire scratchpad + brushPresetThumbnailWidget->setCutoutOverlayRect(QRect(0, 0, brushPresetThumbnailWidget->height(), brushPresetThumbnailWidget->width())); + + + // we will default to reusing the previous preset thumbnail + // have that checked by default, hide the other elements, and load the last preset image + connect(clearBrushPresetThumbnailButton, SIGNAL(clicked(bool)), brushPresetThumbnailWidget, SLOT(fillDefault())); + connect(loadImageIntoThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadImageFromFile())); + + connect(loadScratchPadThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadScratchpadThumbnail())); + connect(loadExistingThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadExistingThumbnail())); + + connect(savePresetButton, SIGNAL(clicked(bool)), this, SLOT(savePreset())); + connect(cancelButton, SIGNAL(clicked(bool)), this, SLOT(close())); +} + +KisPresetSaveWidget::~KisPresetSaveWidget() +{ + +} + +void KisPresetSaveWidget::scratchPadSetup(KisCanvasResourceProvider* resourceProvider) +{ + m_resourceProvider = resourceProvider; + + brushPresetThumbnailWidget->setupScratchPad(m_resourceProvider, Qt::white); +} + +void KisPresetSaveWidget::showDialog() +{ + setModal(true); + + // set the name of the current brush preset area. + KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); + + // UI will look a bit different if we are saving a new brush + if (m_isSavingNewBrush) { + setWindowTitle(i18n("Save New Brush Preset")); + newBrushNameTexField->setVisible(true); + clearBrushPresetThumbnailButton->setVisible(true); + loadImageIntoThumbnailButton->setVisible(true); + currentBrushNameLabel->setVisible(false); + + if (preset) { + newBrushNameTexField->setText(preset->name().append(" ").append(i18n("Copy"))); + } + + + } else { + setWindowTitle(i18n("Save Brush Preset")); + + if (preset) { + currentBrushNameLabel->setText(preset->name()); + } + + newBrushNameTexField->setVisible(false); + currentBrushNameLabel->setVisible(true); + } + + brushPresetThumbnailWidget->paintPresetImage(); + + show(); +} + +void KisPresetSaveWidget::loadImageFromFile() +{ + // create a dialog to retrieve an image file. + KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); + dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); + dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); + QString filename = dialog.filename(); // the filename() returns the entire path & file name, not just the file name + + + if (filename != "") { // empty if "cancel" is pressed + // take that file and load it into the thumbnail are + const QImage imageToLoad(filename); + + brushPresetThumbnailWidget->fillTransparent(); // clear the background in case our new image has transparency + brushPresetThumbnailWidget->paintCustomImage(imageToLoad); + } + +} + +void KisPresetSaveWidget::loadScratchpadThumbnail() +{ + brushPresetThumbnailWidget->paintCustomImage(scratchPadThumbnailArea); +} + +void KisPresetSaveWidget::loadExistingThumbnail() +{ + brushPresetThumbnailWidget->paintPresetImage(); +} + +void KisPresetSaveWidget::setFavoriteResourceManager(KisFavoriteResourceManager * favManager) +{ + m_favoriteResourceManager = favManager; +} + +void KisPresetSaveWidget::savePreset() +{ + KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset(); + if (!curPreset) + return; + + m_favoriteResourceManager->setBlockUpdates(true); + + KisPaintOpPresetSP oldPreset = curPreset->clone(); + oldPreset->load(); + KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); + QString saveLocation = rServer->saveLocation(); + + // if we are saving a new brush, use what we type in for the input + QString presetName = m_isSavingNewBrush ? newBrushNameTexField->text() : curPreset->name(); + + QString currentPresetFileName = saveLocation + presetName + curPreset->defaultFileExtension(); + + // if the preset already exists, make a back up of it + if (rServer->resourceByName(presetName)) { + QString currentDate = QDate::currentDate().toString(Qt::ISODate); + QString currentTime = QTime::currentTime().toString(Qt::ISODate); + QString presetFilename = saveLocation + presetName + "_backup_" + currentDate + "-" + currentTime + oldPreset->defaultFileExtension(); + oldPreset->setFilename(presetFilename); + oldPreset->setName(presetName); + oldPreset->setPresetDirty(false); + oldPreset->setValid(true); + + // add resource to the blacklist + rServer->addResource(oldPreset); + rServer->removeResourceAndBlacklist(oldPreset.data()); + + QStringList tags; + tags = rServer->assignedTagsList(curPreset.data()); + Q_FOREACH (const QString & tag, tags) { + rServer->addTag(oldPreset.data(), tag); + } + } + + if (m_isSavingNewBrush) { + KisPaintOpPresetSP newPreset = curPreset->clone(); + newPreset->setFilename(currentPresetFileName); + newPreset->setName(presetName); + newPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay()); + newPreset->setPresetDirty(false); + newPreset->setValid(true); + + rServer->addResource(newPreset); + + // trying to get brush preset to load after it is created + emit resourceSelected(newPreset.data()); + + } else { + + if (curPreset->filename().contains(saveLocation)==false || curPreset->filename().contains(presetName)==false) { + rServer->removeResourceAndBlacklist(curPreset.data()); + curPreset->setFilename(currentPresetFileName); + curPreset->setName(presetName); + } + + if (!rServer->resourceByFilename(curPreset->filename())){ + //this is necessary so that we can get the preset afterwards. + rServer->addResource(curPreset, false, false); + rServer->removeFromBlacklist(curPreset.data()); + } + + curPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay()); + curPreset->save(); + curPreset->load(); + } + + + // HACK ALERT! the server does not notify the observers + // automatically, so we need to call theupdate manually! + rServer->tagCategoryMembersChanged(); + + m_favoriteResourceManager->setBlockUpdates(false); + + close(); // we are done... so close the save brush dialog + +} + +void KisPresetSaveWidget::saveScratchPadThumbnailArea(QImage image) +{ + scratchPadThumbnailArea = image; +} + + +void KisPresetSaveWidget::isSavingNewBrush(bool newBrush) +{ + m_isSavingNewBrush = newBrush; +} + + +#include "moc_kis_paintop_presets_save.cpp" diff --git a/libs/ui/widgets/kis_paintop_presets_save.h b/libs/ui/widgets/kis_paintop_presets_save.h new file mode 100644 index 0000000000..5f03a95253 --- /dev/null +++ b/libs/ui/widgets/kis_paintop_presets_save.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + * Copyright (C) 2017 Scott Petrovic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef KIS_PAINTOP_PRESETS_SAVE_H +#define KIS_PAINTOP_PRESETS_SAVE_H + +#include +#include + +#include "ui_wdgsavebrushpreset.h" +#include "kis_canvas_resource_provider.h" +#include "kis_favorite_resource_manager.h" + +class KisPaintOpPresetSaveDialog : public QDialog , public Ui::WdgSaveBrushPreset +{ + Q_OBJECT + + + +public: + KisPaintOpPresetSaveDialog(QWidget* parent) : QDialog(parent) { + setupUi(this); + } +}; + + +class KisPresetSaveWidget : public KisPaintOpPresetSaveDialog +{ + Q_OBJECT + +public: + KisPresetSaveWidget(QWidget* parent); + virtual ~KisPresetSaveWidget(); + + void showDialog(); + + void isSavingNewBrush(bool newBrush); + + void scratchPadSetup(KisCanvasResourceProvider* resourceProvider); + void saveScratchPadThumbnailArea(const QImage image); + KisCanvasResourceProvider* m_resourceProvider; + + void setFavoriteResourceManager(KisFavoriteResourceManager * favManager); + +Q_SIGNALS: + void resourceSelected(KoResource* resource); + + +public Q_SLOTS: + + void loadImageFromFile(); + void savePreset(); + void loadScratchpadThumbnail(); + void loadExistingThumbnail(); + + +private: + bool m_isSavingNewBrush; + KisFavoriteResourceManager * m_favoriteResourceManager; + QImage scratchPadThumbnailArea; +}; + + +#endif diff --git a/libs/ui/widgets/kis_progress_widget.cpp b/libs/ui/widgets/kis_progress_widget.cpp index 5c07a34993..ec1734de9e 100644 --- a/libs/ui/widgets/kis_progress_widget.cpp +++ b/libs/ui/widgets/kis_progress_widget.cpp @@ -1,95 +1,100 @@ /* * Copyright (c) 2002 Patrick Julien * 2004 Adrian Page * 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 "kis_progress_widget.h" #include #include #include #include #include #include #include #include KisProgressWidget::KisProgressWidget(QWidget* parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); m_cancelButton = new QToolButton(this); m_cancelButton->setIcon(KisIconUtils::loadIcon("process-stop")); QSizePolicy sizePolicy = m_cancelButton->sizePolicy(); sizePolicy.setVerticalPolicy(QSizePolicy::Ignored); m_cancelButton->setSizePolicy(sizePolicy); connect(m_cancelButton, SIGNAL(clicked()), this, SLOT(cancel())); m_progressBar = new KoProgressBar(this); + // fixme:connect to the visibility changed signal if exists connect(m_progressBar, SIGNAL(valueChanged(int)), SLOT(correctVisibility(int))); layout->addWidget(m_progressBar); layout->addWidget(m_cancelButton); layout->setContentsMargins(0, 0, 0, 0); m_progressBar->setVisible(false); m_cancelButton->setVisible(false); setMaximumWidth(225); setMinimumWidth(225); } KisProgressWidget::~KisProgressWidget() { } KoProgressProxy* KisProgressWidget::progressProxy() { return m_progressBar; } void KisProgressWidget::cancel() { Q_FOREACH (KoProgressUpdater* updater, m_activeUpdaters) { updater->cancel(); } emit sigCancellationRequested(); } void KisProgressWidget::correctVisibility(int progressValue) { - bool visibility = progressValue >= m_progressBar->minimum() && - progressValue < m_progressBar->maximum(); + // TODO: this check duplicates code in KoProgressBar::setValue() + + const bool visibility = + m_progressBar->minimum() == m_progressBar->maximum() || + (progressValue >= m_progressBar->minimum() && + progressValue < m_progressBar->maximum()); m_progressBar->setVisible(visibility); m_cancelButton->setVisible(visibility); } void KisProgressWidget::detachUpdater(KoProgressUpdater* updater) { m_activeUpdaters.removeOne(updater); } void KisProgressWidget::attachUpdater(KoProgressUpdater* updater) { m_activeUpdaters << updater; } diff --git a/libs/ui/widgets/kis_scratch_pad.cpp b/libs/ui/widgets/kis_scratch_pad.cpp index b18fa65da0..dc62ec1fc7 100644 --- a/libs/ui/widgets/kis_scratch_pad.cpp +++ b/libs/ui/widgets/kis_scratch_pad.cpp @@ -1,471 +1,509 @@ /* This file is part of the KDE project * Copyright 2010 (C) Boudewijn Rempt * Copyright 2011 (C) Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_scratch_pad.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_image.h" #include "kis_undo_stores.h" #include "kis_update_scheduler.h" #include "kis_post_execution_undo_adapter.h" #include "kis_scratch_pad_event_filter.h" #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "kis_image_patch.h" #include "kis_canvas_widget_base.h" #include "kis_layer_projection_plane.h" #include "kis_node_graph_listener.h" class KisScratchPadNodeListener : public KisNodeGraphListener { public: KisScratchPadNodeListener(KisScratchPad *scratchPad) : m_scratchPad(scratchPad) { } void requestProjectionUpdate(KisNode *node, const QRect& rect, bool resetAnimationCache) override { KisNodeGraphListener::requestProjectionUpdate(node, rect, resetAnimationCache); QMutexLocker locker(&m_lock); m_scratchPad->imageUpdated(rect); } private: KisScratchPad *m_scratchPad; QMutex m_lock; }; class KisScratchPadDefaultBounds : public KisDefaultBounds { public: KisScratchPadDefaultBounds(KisScratchPad *scratchPad) : m_scratchPad(scratchPad) { } ~KisScratchPadDefaultBounds() override {} QRect bounds() const override { return m_scratchPad->imageBounds(); } private: Q_DISABLE_COPY(KisScratchPadDefaultBounds) KisScratchPad *m_scratchPad; }; KisScratchPad::KisScratchPad(QWidget *parent) : QWidget(parent) , m_toolMode(HOVERING) , m_paintLayer(0) , m_displayProfile(0) , m_resourceProvider(0) { setAutoFillBackground(false); setMouseTracking(true); m_cursor = KisCursor::load("tool_freehand_cursor.png", 5, 5); setCursor(m_cursor); KisConfig cfg; QImage checkImage = KisCanvasWidgetBase::createCheckersImage(cfg.checkSize()); m_checkBrush = QBrush(checkImage); // We are not supposed to use updates here, // so just set the listener to null m_updateScheduler = new KisUpdateScheduler(0); m_undoStore = new KisSurrogateUndoStore(); m_undoAdapter = new KisPostExecutionUndoAdapter(m_undoStore, m_updateScheduler); m_nodeListener = new KisScratchPadNodeListener(this); connect(this, SIGNAL(sigUpdateCanvas(QRect)), SLOT(slotUpdateCanvas(QRect)), Qt::QueuedConnection); // filter will be deleted by the QObject hierarchy m_eventFilter = new KisScratchPadEventFilter(this); m_infoBuilder = new KisPaintingInformationBuilder(); m_helper = new KisToolFreehandHelper(m_infoBuilder); m_scaleBorderWidth = 1; } KisScratchPad::~KisScratchPad() { delete m_helper; delete m_infoBuilder; delete m_undoAdapter; delete m_undoStore; delete m_updateScheduler; delete m_nodeListener; } KisScratchPad::Mode KisScratchPad::modeFromButton(Qt::MouseButton button) const { return button == Qt::NoButton ? HOVERING : button == Qt::MidButton ? PANNING : button == Qt::RightButton ? PICKING : PAINTING; } void KisScratchPad::pointerPress(KoPointerEvent *event) { if (m_toolMode != HOVERING) return; m_toolMode = modeFromButton(event->button()); if (m_toolMode == PAINTING) { beginStroke(event); event->accept(); } else if (m_toolMode == PANNING) { beginPan(event); event->accept(); } else if (m_toolMode == PICKING) { pick(event); event->accept(); } } void KisScratchPad::pointerRelease(KoPointerEvent *event) { if (modeFromButton(event->button()) != m_toolMode) return; if (m_toolMode == PAINTING) { endStroke(event); m_toolMode = HOVERING; event->accept(); } else if (m_toolMode == PANNING) { endPan(event); m_toolMode = HOVERING; event->accept(); } else if (m_toolMode == PICKING) { event->accept(); m_toolMode = HOVERING; } } void KisScratchPad::pointerMove(KoPointerEvent *event) { m_helper->cursorMoved(documentToWidget().map(event->point)); if (m_toolMode == PAINTING) { doStroke(event); event->accept(); } else if (m_toolMode == PANNING) { doPan(event); event->accept(); } else if (m_toolMode == PICKING) { pick(event); event->accept(); } } void KisScratchPad::beginStroke(KoPointerEvent *event) { KoCanvasResourceManager *resourceManager = m_resourceProvider->resourceManager(); m_helper->initPaint(event, documentToWidget().map(event->point), resourceManager, 0, 0, m_updateScheduler, m_paintLayer, m_paintLayer->paintDevice()->defaultBounds()); } void KisScratchPad::doStroke(KoPointerEvent *event) { m_helper->paintEvent(event); } void KisScratchPad::endStroke(KoPointerEvent *event) { Q_UNUSED(event); m_helper->endPaint(); } void KisScratchPad::beginPan(KoPointerEvent *event) { setCursor(QCursor(Qt::ClosedHandCursor)); m_panDocPoint = event->point; } void KisScratchPad::doPan(KoPointerEvent *event) { QPointF docOffset = event->point - m_panDocPoint; m_translateTransform.translate(-docOffset.x(), -docOffset.y()); updateTransformations(); update(); } void KisScratchPad::endPan(KoPointerEvent *event) { Q_UNUSED(event); setCursor(m_cursor); } void KisScratchPad::pick(KoPointerEvent *event) { KoColor color; if (KisToolUtils::pick(m_paintLayer->projection(), event->point.toPoint(), &color)) { emit colorSelected(color); } } void KisScratchPad::setOnScreenResolution(qreal scaleX, qreal scaleY) { m_scaleBorderWidth = BORDER_SIZE(qMax(scaleX, scaleY)); m_scaleTransform = QTransform::fromScale(scaleX, scaleY); updateTransformations(); update(); } QTransform KisScratchPad::documentToWidget() const { return m_translateTransform.inverted() * m_scaleTransform; } QTransform KisScratchPad::widgetToDocument() const { return m_scaleTransform.inverted() * m_translateTransform; } void KisScratchPad::updateTransformations() { m_eventFilter->setWidgetToDocumentTransform(widgetToDocument()); } QRect KisScratchPad::imageBounds() const { return widgetToDocument().mapRect(rect()); } void KisScratchPad::imageUpdated(const QRect &rect) { emit sigUpdateCanvas(documentToWidget().mapRect(QRectF(rect)).toAlignedRect()); } void KisScratchPad::slotUpdateCanvas(const QRect &rect) { update(rect); } void KisScratchPad::paintEvent ( QPaintEvent * event ) { if(!m_paintLayer) return; QRectF imageRect = widgetToDocument().mapRect(QRectF(event->rect())); QRect alignedImageRect = imageRect.adjusted(-m_scaleBorderWidth, -m_scaleBorderWidth, m_scaleBorderWidth, m_scaleBorderWidth).toAlignedRect(); QPointF offset = alignedImageRect.topLeft(); m_paintLayer->projectionPlane()->recalculate(alignedImageRect, m_paintLayer); KisPaintDeviceSP projection = m_paintLayer->projection(); QImage image = projection->convertToQImage(m_displayProfile, alignedImageRect.x(), alignedImageRect.y(), alignedImageRect.width(), alignedImageRect.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QPainter gc(this); gc.fillRect(event->rect(), m_checkBrush); gc.setRenderHints(QPainter::SmoothPixmapTransform); gc.drawImage(QRectF(event->rect()), image, imageRect.translated(-offset)); QBrush brush(Qt::lightGray); QPen pen(brush, 1, Qt::DotLine); gc.setPen(pen); if (m_cutoutOverlay.isValid()) { gc.drawRect(m_cutoutOverlay); } if(!isEnabled()) { QColor color(Qt::lightGray); color.setAlphaF(0.5); QBrush disabledBrush(color); gc.fillRect(event->rect(), disabledBrush); } gc.end(); } void KisScratchPad::setupScratchPad(KisCanvasResourceProvider* resourceProvider, const QColor &defaultColor) { m_resourceProvider = resourceProvider; KisConfig cfg; setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this))); connect(m_resourceProvider, SIGNAL(sigDisplayProfileChanged(const KoColorProfile*)), SLOT(setDisplayProfile(const KoColorProfile*))); connect(m_resourceProvider, SIGNAL(sigOnScreenResolutionChanged(qreal,qreal)), SLOT(setOnScreenResolution(qreal,qreal))); connect(this, SIGNAL(colorSelected(const KoColor&)), m_resourceProvider, SLOT(slotSetFGColor(const KoColor&))); m_defaultColor = KoColor(defaultColor, KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP paintDevice = new KisPaintDevice(m_defaultColor.colorSpace(), "scratchpad"); m_paintLayer = new KisPaintLayer(0, "ScratchPad", OPACITY_OPAQUE_U8, paintDevice); m_paintLayer->setGraphListener(m_nodeListener); m_paintLayer->paintDevice()->setDefaultBounds(new KisScratchPadDefaultBounds(this)); fillDefault(); } void KisScratchPad::setCutoutOverlayRect(const QRect& rc) { m_cutoutOverlay = rc; } QImage KisScratchPad::cutoutOverlay() const { if(!m_paintLayer) return QImage(); KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); + QRect rc = widgetToDocument().mapRect(m_cutoutOverlay); QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QImage scaledImage = rawImage.scaled(m_cutoutOverlay.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); return scaledImage; } void KisScratchPad::setPresetImage(const QImage& image) { m_presetImage = image; } + +void KisScratchPad::paintCustomImage(const QImage& loadedImage) +{ + // this is 99% copied from the normal paintPresetImage() + // we dont' want to save over the preset image, so we don't + // want to store it in the m_presetImage + if(!m_paintLayer) return; + KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); + + + QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay); + QRect imageRect(QPoint(), overlayRect.size()); + + QImage scaledImage = loadedImage.scaled(overlayRect.size(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace()); + device->convertFromQImage(scaledImage, 0); + + KisPainter painter(paintDevice); + painter.bitBlt(overlayRect.topLeft(), device, imageRect); + update(); +} + void KisScratchPad::paintPresetImage() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); + QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay); QRect imageRect(QPoint(), overlayRect.size()); QImage scaledImage = m_presetImage.scaled(overlayRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace()); device->convertFromQImage(scaledImage, 0); KisPainter painter(paintDevice); painter.bitBlt(overlayRect.topLeft(), device, imageRect); update(); } void KisScratchPad::setDisplayProfile(const KoColorProfile *colorProfile) { if (colorProfile) { m_displayProfile = colorProfile; QWidget::update(); } } void KisScratchPad::fillDefault() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); paintDevice->setDefaultPixel(m_defaultColor); paintDevice->clear(); update(); } +void KisScratchPad::fillTransparent() { + if(!m_paintLayer) return; + KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); + + QColor transQColor(0,0,0,0); + KoColor transparentColor(transQColor, KoColorSpaceRegistry::instance()->rgb8()); + transparentColor.setOpacity(0.0); + + paintDevice->setDefaultPixel(transparentColor); + paintDevice->clear(); + update(); +} + void KisScratchPad::fillGradient() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KoAbstractGradient* gradient = m_resourceProvider->currentGradient(); QRect gradientRect = widgetToDocument().mapRect(rect()); paintDevice->clear(); KisGradientPainter painter(paintDevice); painter.setGradient(gradient); painter.setGradientShape(KisGradientPainter::GradientShapeLinear); painter.paintGradient(gradientRect.topLeft(), gradientRect.bottomRight(), KisGradientPainter::GradientRepeatNone, 0.2, false, gradientRect.left(), gradientRect.top(), gradientRect.width(), gradientRect.height()); update(); } void KisScratchPad::fillBackground() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); paintDevice->setDefaultPixel(m_resourceProvider->bgColor()); paintDevice->clear(); update(); } void KisScratchPad::fillLayer() { // TODO } diff --git a/libs/ui/widgets/kis_scratch_pad.h b/libs/ui/widgets/kis_scratch_pad.h index 3bced1f676..74fc07e54e 100644 --- a/libs/ui/widgets/kis_scratch_pad.h +++ b/libs/ui/widgets/kis_scratch_pad.h @@ -1,171 +1,178 @@ /* This file is part of the KDE project * Copyright 2010 (C) Boudewijn Rempt * Copyright 2011 (C) Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_SCRATCH_PAD_H #define KIS_SCRATCH_PAD_H #include #include #include #include #include #include #include class QColor; class KoColorProfile; class KoPointerEvent; class KisCanvasResourceProvider; class KisUpdateScheduler; class KisUndoStore; class KisPostExecutionUndoAdapter; class KisScratchPadEventFilter; class KisPaintingInformationBuilder; class KisToolFreehandHelper; class KisNodeGraphListener; /** * A scratchpad is a painting canvas with only one zoomlevel and based on * a paint layer, not on a KisImage. It can have a blank, tiled background or * a gradient background. */ class KRITAUI_EXPORT KisScratchPad : public QWidget { Q_OBJECT public: void setupScratchPad(KisCanvasResourceProvider* resourceProvider, const QColor &defaultColor); KisScratchPad(QWidget *parent = 0); ~KisScratchPad() override; /// set the specified rect as the area taken for @see cutoutOverlay void setCutoutOverlayRect(const QRect&rc); /// return the contents of the area under the cutoutOverlay rect QImage cutoutOverlay() const; // A callback for our own node graph listener void imageUpdated(const QRect &rect); // A callback for scratch pad default bounds QRect imageBounds() const; // Called by the event filter void pointerPress(KoPointerEvent *event); void pointerRelease(KoPointerEvent *event); void pointerMove(KoPointerEvent *event); public Q_SLOTS: void fillDefault(); void fillGradient(); void fillBackground(); void fillLayer(); + void fillTransparent(); /** * Set the icon of the current preset */ void setPresetImage(const QImage& image); /** * Paint the icon of the current preset inside the * cutout overlay * * \see setPresetImage */ void paintPresetImage(); + /** + * Paint the icon of a custom image that is being loaded + * + */ + void paintCustomImage(const QImage& loadedImage); + private Q_SLOTS: void setOnScreenResolution(qreal scaleX, qreal scaleY); void setDisplayProfile(const KoColorProfile* colorProfile); void slotUpdateCanvas(const QRect &rect); Q_SIGNALS: void colorSelected(const KoColor& color); void sigUpdateCanvas(const QRect &rect); protected: void paintEvent ( QPaintEvent * event ) override; private: void beginStroke(KoPointerEvent *event); void doStroke(KoPointerEvent *event); void endStroke(KoPointerEvent *event); void beginPan(KoPointerEvent *event); void doPan(KoPointerEvent *event); void endPan(KoPointerEvent *event); void pick(KoPointerEvent *event); void updateTransformations(); QTransform documentToWidget() const; QTransform widgetToDocument() const; private: enum Mode { PAINTING, HOVERING, PANNING, PICKING }; Mode modeFromButton(Qt::MouseButton button) const; private: KoColor m_defaultColor; Mode m_toolMode; KisPaintLayerSP m_paintLayer; const KoColorProfile* m_displayProfile; QCursor m_cursor; QRect m_cutoutOverlay; QBrush m_checkBrush; KisCanvasResourceProvider* m_resourceProvider; KisUpdateScheduler *m_updateScheduler; KisUndoStore *m_undoStore; KisPostExecutionUndoAdapter *m_undoAdapter; KisNodeGraphListener *m_nodeListener; KisScratchPadEventFilter *m_eventFilter; KisToolFreehandHelper *m_helper; KisPaintingInformationBuilder *m_infoBuilder; QTransform m_scaleTransform; QTransform m_translateTransform; QPointF m_panDocPoint; int m_scaleBorderWidth; QImage m_presetImage; }; #endif // KIS_SCRATCH_PAD_H diff --git a/libs/widgetutils/CMakeLists.txt b/libs/widgetutils/CMakeLists.txt index a8577f39f3..a7fb1c6079 100644 --- a/libs/widgetutils/CMakeLists.txt +++ b/libs/widgetutils/CMakeLists.txt @@ -1,130 +1,131 @@ add_subdirectory(tests) configure_file(xmlgui/config-xmlgui.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-xmlgui.h ) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/config) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/xmlgui) set(kritawidgetutils_LIB_SRCS WidgetUtilsDebug.cpp kis_icon_utils.cpp kis_action_registry.cpp KisActionsSnapshot.cpp KoGroupButton.cpp + KoProgressProxy.cpp KoProgressBar.cpp KoProgressUpdater.cpp KoUpdater.cpp KoUpdaterPrivate_p.cpp KoProperties.cpp KoFileDialog.cpp KoResourcePaths.cpp kis_num_parser.cpp kis_spin_box_unit_manager.cpp config/kcolorscheme.cpp config/kcolorschememanager.cpp config/khelpclient.cpp config/klanguagebutton.cpp config/krecentfilesaction.cpp config/kstandardaction.cpp xmlgui/KisShortcutsEditorItem.cpp xmlgui/KisShortcutEditWidget.cpp xmlgui/KisShortcutsEditorDelegate.cpp xmlgui/KisShortcutsDialog.cpp xmlgui/KisShortcutsDialog_p.cpp xmlgui/KisShortcutsEditor.cpp xmlgui/KisShortcutsEditor_p.cpp xmlgui/kshortcutschemeseditor.cpp xmlgui/kshortcutschemeshelper.cpp xmlgui/kaboutkdedialog_p.cpp xmlgui/kactioncategory.cpp xmlgui/kactioncollection.cpp xmlgui/kactionconflictdetector.cpp xmlgui/kbugreport.cpp xmlgui/kcheckaccelerators.cpp xmlgui/kedittoolbar.cpp xmlgui/kgesture.cpp xmlgui/kgesturemap.cpp xmlgui/khelpmenu.cpp xmlgui/kkeysequencewidget.cpp xmlgui/kmainwindow.cpp xmlgui/kmenumenuhandler_p.cpp xmlgui/kshortcutwidget.cpp xmlgui/kswitchlanguagedialog_p.cpp xmlgui/ktoggletoolbaraction.cpp xmlgui/ktoolbar.cpp xmlgui/ktoolbarhandler.cpp xmlgui/kundoactions.cpp xmlgui/kxmlguibuilder.cpp xmlgui/kxmlguiclient.cpp xmlgui/kxmlguifactory.cpp xmlgui/kxmlguifactory_p.cpp xmlgui/kxmlguiversionhandler.cpp xmlgui/kxmlguiwindow.cpp ) if (HAVE_DBUS) set(kritawidgetutils_LIB_SRCS ${kritawidgetutils_LIB_SRCS} xmlgui/kmainwindowiface.cpp ) endif() ki18n_wrap_ui(kritawidgetutils_LIB_SRCS xmlgui/KisShortcutsDialog.ui xmlgui/kshortcutwidget.ui ) qt5_add_resources(kritawidgetutils_LIB_SRCS xmlgui/kxmlgui.qrc) add_library(kritawidgetutils SHARED ${kritawidgetutils_LIB_SRCS}) target_include_directories(kritawidgetutils PUBLIC $ $ ) generate_export_header(kritawidgetutils BASE_NAME kritawidgetutils) if (HAVE_DBUS) set (KRITA_WIDGET_UTILS_EXTRA_LIBS ${KRITA_WIDGET_UTILS_EXTRA_LIBS} Qt5::DBus) endif () if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) set(KRITA_WIDGET_UTILS_EXTRA_LIBS ${KRITA_WIDGET_UTILS_EXTRA_LIBS} ${FOUNDATION_LIBRARY}) endif () target_link_libraries(kritawidgetutils PUBLIC Qt5::Widgets Qt5::Gui Qt5::Xml Qt5::Core KF5::ItemViews kritaglobal PRIVATE Qt5::PrintSupport KF5::I18n KF5::ConfigCore KF5::CoreAddons KF5::ConfigGui KF5::GuiAddons KF5::WidgetsAddons KF5::WindowSystem kritaplugin kritaodf ${KRITA_WIDGET_UTILS_EXTRA_LIBS} ) set_target_properties(kritawidgetutils PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritawidgetutils ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/widgetutils/KoProgressBar.cpp b/libs/widgetutils/KoProgressBar.cpp index 8f14da2a8f..4cbcd49f61 100644 --- a/libs/widgetutils/KoProgressBar.cpp +++ b/libs/widgetutils/KoProgressBar.cpp @@ -1,56 +1,61 @@ /* 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 "KoProgressBar.h" KoProgressBar::KoProgressBar(QWidget *parent) : QProgressBar(parent) { } KoProgressBar::~KoProgressBar() { } int KoProgressBar::maximum() const { return QProgressBar::maximum(); } void KoProgressBar::setValue(int value) { QProgressBar::setValue(value); - if (value >= minimum() && value < maximum()) { + // we also show the bar if it is in undetermined state + if (minimum() == maximum() || + (value >= minimum() && value < maximum())) { + setVisible( true ); + } else { + emit done(); setVisible( false ); } } void KoProgressBar::setRange(int minimum, int maximum) { QProgressBar::setRange(minimum, maximum); } void KoProgressBar::setFormat(const QString &format) { QProgressBar::setFormat(format); } diff --git a/libs/image/kis_name_server.h b/libs/widgetutils/KoProgressProxy.cpp similarity index 66% copy from libs/image/kis_name_server.h copy to libs/widgetutils/KoProgressProxy.cpp index aed1e92d63..d5c220dccd 100644 --- a/libs/image/kis_name_server.h +++ b/libs/widgetutils/KoProgressProxy.cpp @@ -1,39 +1,34 @@ /* - * Copyright (c) 2002 Patrick Julien + * 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 KIS_NAMESERVER_H_ -#define KIS_NAMESERVER_H_ + +#include "KoProgressProxy.h" #include -#include -class KRITAIMAGE_EXPORT KisNameServer +void KoProgressProxy::setAutoNestedName(const QString &name) { -public: - KisNameServer(qint32 seed = 1); - ~KisNameServer(); - - qint32 number(); - qint32 currentSeed() const; - void rollback(); - -private: - qint32 m_generator; -}; - -#endif // KIS_NAMESERVER_H_ - + if (name.isEmpty()) { + setFormat("%p%"); + } else { + if (maximum() > 0) { + setFormat(QString("%1: %p%").arg(name)); + } else { + setFormat(name); + } + } +} diff --git a/libs/widgetutils/KoProgressProxy.h b/libs/widgetutils/KoProgressProxy.h index d0d869c343..7aca0ae1ee 100644 --- a/libs/widgetutils/KoProgressProxy.h +++ b/libs/widgetutils/KoProgressProxy.h @@ -1,46 +1,47 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2009 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_PROGRESS_PROXY #define KO_PROGRESS_PROXY #include "kritawidgetutils_export.h" class QString; /** * A proxy interface for a real progress status reporting thing, either * a widget such as a KoProgressProxy childclass that also inherits this * interface, or something that prints progress to stdout. */ class KRITAWIDGETUTILS_EXPORT KoProgressProxy { public: virtual ~KoProgressProxy() { } virtual int maximum() const = 0; virtual void setValue(int value) = 0; virtual void setRange(int minimum, int maximum) = 0; virtual void setFormat(const QString &format) = 0; + virtual void setAutoNestedName(const QString &name); }; #endif diff --git a/libs/widgetutils/KoProgressUpdater.cpp b/libs/widgetutils/KoProgressUpdater.cpp index bf523cd7eb..f5fe95fa28 100644 --- a/libs/widgetutils/KoProgressUpdater.cpp +++ b/libs/widgetutils/KoProgressUpdater.cpp @@ -1,194 +1,348 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2009 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoProgressUpdater.h" #include #include #include #include #include "KoUpdaterPrivate_p.h" #include "KoUpdater.h" #include "KoProgressProxy.h" - -// 4 updates per second should be enough -#define PROGRESSUPDATER_GUITIMERINTERVAL 250 +#include class Q_DECL_HIDDEN KoProgressUpdater::Private { public: - Private(KoProgressUpdater *_parent, KoProgressProxy *p, Mode _mode) - : parent(_parent) - , progressBar(p) + Private(KoProgressUpdater *_q, KoProgressProxy *proxy, QPointer parentUpdater, Mode _mode) + : q(_q) + , parentProgressProxy(proxy) + , parentUpdater(parentUpdater) , mode(_mode) - , totalWeight(0) , currentProgress(0) , updated(false) - , updateGuiTimer(_parent) + , updateGuiTimer(_q) , canceled(false) { } - KoProgressUpdater *parent; - KoProgressProxy *progressBar; + KoProgressUpdater *q; + +private: + KoProgressProxy *parentProgressProxy; + QPointer parentUpdater; + +public: Mode mode; - int totalWeight; - int currentProgress; + int currentProgress = 0; + bool isUndefinedState = false; bool updated; // is true whenever the progress needs to be recomputed QTimer updateGuiTimer; // fires regulary to update the progress bar widget QList > subtasks; - QList > subTaskWrappers; // We delete these bool canceled; + int updateInterval = 250; // ms, 4 updates per second should be enough + bool autoNestNames = false; + QString taskName; + int taskMax = -1; + bool isStarted = false; + + void updateParentText(); + void clearState(); + + KoProgressProxy* progressProxy() { + return parentUpdater ? parentUpdater : parentProgressProxy; + } }; // NOTE: do not make the KoProgressUpdater object part of the QObject // hierarchy. Do not make KoProgressProxy its parent (note that KoProgressProxy // is not necessarily castable to QObject ). This prevents proper functioning // of progress reporting in multi-threaded environments. -KoProgressUpdater::KoProgressUpdater(KoProgressProxy *progressBar, Mode mode) - : d (new Private(this, progressBar, mode)) +KoProgressUpdater::KoProgressUpdater(KoProgressProxy *progressProxy, Mode mode) + : d (new Private(this, progressProxy, 0, mode)) +{ + KIS_ASSERT_RECOVER_RETURN(progressProxy); + connect(&d->updateGuiTimer, SIGNAL(timeout()), SLOT(updateUi())); +} + +KoProgressUpdater::KoProgressUpdater(QPointer updater) + : d (new Private(this, 0, updater, Unthreaded)) { - Q_ASSERT(d->progressBar); + KIS_ASSERT_RECOVER_RETURN(updater); connect(&d->updateGuiTimer, SIGNAL(timeout()), SLOT(updateUi())); } KoProgressUpdater::~KoProgressUpdater() { - d->progressBar->setValue(d->progressBar->maximum()); + if (d->progressProxy()) { + d->progressProxy()->setRange(0, d->taskMax); + d->progressProxy()->setValue(d->progressProxy()->maximum()); + } // make sure to stop the timer to avoid accessing // the data we are going to delete right now d->updateGuiTimer.stop(); qDeleteAll(d->subtasks); d->subtasks.clear(); - qDeleteAll(d->subTaskWrappers); - d->subTaskWrappers.clear(); - delete d; } void KoProgressUpdater::start(int range, const QString &text) { - d->updateGuiTimer.start(PROGRESSUPDATER_GUITIMERINTERVAL); - - qDeleteAll(d->subtasks); - d->subtasks.clear(); - - qDeleteAll(d->subTaskWrappers); - d->subTaskWrappers.clear(); - - d->progressBar->setRange(0, range-1); - d->progressBar->setValue(0); - - if(!text.isEmpty()) { - d->progressBar->setFormat(text); + d->clearState(); + d->taskName = text; + d->taskMax = range - 1; + d->isStarted = true; + + if (d->progressProxy()) { + d->progressProxy()->setRange(0, d->taskMax); + d->progressProxy()->setValue(0); + d->updateParentText(); } - d->totalWeight = 0; - d->canceled = false; + + d->updateGuiTimer.start(d->updateInterval); } QPointer KoProgressUpdater::startSubtask(int weight, - const QString &name) + const QString &name, + bool isPersistent) { - KoUpdaterPrivate *p = new KoUpdaterPrivate(this, weight, name); - d->totalWeight += weight; + if (!d->isStarted) { + // lazy initialization for intermediate proxies + start(); + } + + KoUpdaterPrivate *p = new KoUpdaterPrivate(this, weight, name, isPersistent); d->subtasks.append(p); connect(p, SIGNAL(sigUpdated()), SLOT(update())); - QPointer updater = new KoUpdater(p); - d->subTaskWrappers.append(updater); + QPointer updater = p->connectedUpdater(); if (!d->updateGuiTimer.isActive()) { // we maybe need to restart the timer if it was stopped in updateUi() cause // other sub-tasks created before this one finished already. - d->updateGuiTimer.start(PROGRESSUPDATER_GUITIMERINTERVAL); + d->updateGuiTimer.start(d->updateInterval); } + d->updated = true; return updater; } +void KoProgressUpdater::removePersistentSubtask(QPointer updater) +{ + for (auto it = d->subtasks.begin(); it != d->subtasks.end();) { + if ((*it)->connectedUpdater() != updater) { + ++it; + } else { + KIS_SAFE_ASSERT_RECOVER_NOOP((*it)->isPersistent()); + (*it)->deleteLater(); + it = d->subtasks.erase(it); + break; + } + } + + updateUi(); +} + void KoProgressUpdater::cancel() { Q_FOREACH (KoUpdaterPrivate *updater, d->subtasks) { updater->setProgress(100); - updater->interrupt(); + updater->setInterrupted(true); } d->canceled = true; updateUi(); } void KoProgressUpdater::update() { d->updated = true; if (d->mode == Unthreaded) { qApp->processEvents(); } + + if (!d->updateGuiTimer.isActive()) { + d->updateGuiTimer.start(d->updateInterval); + } } void KoProgressUpdater::updateUi() { // This function runs in the app main thread. All the progress // updates arrive at the KoUpdaterPrivate instances through // queued connections, so until we relinguish control to the // event loop, the progress values cannot change, and that // won't happen until we return from this function (which is // triggered by a timer) + /** + * We shouldn't let progress updater to interfere the progress + * reporting when it is not initialized. + */ + if (d->subtasks.isEmpty()) return; + if (d->updated) { int totalProgress = 0; + int totalWeight = 0; + d->isUndefinedState = false; + Q_FOREACH (QPointer updater, d->subtasks) { if (updater->interrupted()) { d->currentProgress = -1; - return; + break; } - int progress = updater->progress(); - if (progress > 100 || progress < 0) { - progress = updater->progress(); + if (!updater->hasValidRange()) { + totalWeight = 0; + totalProgress = 0; + d->isUndefinedState = true; + break; } - totalProgress += progress *updater->weight(); + if (updater->isPersistent() && updater->isCompleted()) { + continue; + } + + const int progress = qBound(0, updater->progress(), 100); + totalProgress += progress * updater->weight(); + totalWeight += updater->weight(); } - d->currentProgress = totalProgress / d->totalWeight; + const int progressPercent = totalWeight > 0 ? totalProgress / totalWeight : -1; + + d->currentProgress = + d->taskMax == 99 ? + progressPercent : + qRound(qreal(progressPercent) * d->taskMax / 99.0); + d->updated = false; } - if (d->currentProgress == -1) { - d->progressBar->setValue( d->progressBar->maximum() ); - // should we hide the progressbar after a little while? - return; + if (d->progressProxy()) { + if (!d->isUndefinedState) { + d->progressProxy()->setRange(0, d->taskMax); + + if (d->currentProgress == -1) { + d->currentProgress = d->progressProxy()->maximum(); + } + + if (d->currentProgress >= d->progressProxy()->maximum()) { + // we're done + d->updateGuiTimer.stop(); + d->clearState(); + } else { + d->progressProxy()->setValue(d->currentProgress); + } + } else { + d->progressProxy()->setRange(0,0); + d->progressProxy()->setValue(0); + } + + d->updateParentText(); } +} + +void KoProgressUpdater::Private::updateParentText() +{ + if (!progressProxy()) return; + + QString actionName = taskName; + + if (autoNestNames) { + Q_FOREACH (QPointer updater, subtasks) { + + if (updater->isPersistent() && updater->isCompleted()) { + continue; + } + + if (updater->progress() < 100) { + const QString subTaskName = updater->mergedSubTaskName(); + + if (!subTaskName.isEmpty()) { + if (actionName.isEmpty()) { + actionName = subTaskName; + } else { + actionName = QString("%1: %2").arg(actionName).arg(subTaskName); + } + } + break; + } + } + progressProxy()->setAutoNestedName(actionName); + } else { + progressProxy()->setFormat(actionName); + } + +} - if (d->currentProgress >= d->progressBar->maximum()) { - // we're done - d->updateGuiTimer.stop(); // 10 updates/second should be enough? +void KoProgressUpdater::Private::clearState() +{ + for (auto it = subtasks.begin(); it != subtasks.end();) { + if (!(*it)->isPersistent()) { + (*it)->deleteLater(); + it = subtasks.erase(it); + } else { + if ((*it)->interrupted()) { + (*it)->setInterrupted(false); + } + ++it; + } } - d->progressBar->setValue(d->currentProgress); + + progressProxy()->setRange(0, taskMax); + progressProxy()->setValue(progressProxy()->maximum()); + + canceled = false; } bool KoProgressUpdater::interrupted() const { return d->canceled; } + +void KoProgressUpdater::setUpdateInterval(int ms) +{ + d->updateInterval = ms; + + if (d->updateGuiTimer.isActive()) { + d->updateGuiTimer.start(d->updateInterval); + } +} + +int KoProgressUpdater::updateInterval() const +{ + return d->updateInterval; +} + +void KoProgressUpdater::setAutoNestNames(bool value) +{ + d->autoNestNames = value; + update(); +} + +bool KoProgressUpdater::autoNestNames() const +{ + return d->autoNestNames; +} diff --git a/libs/widgetutils/KoProgressUpdater.h b/libs/widgetutils/KoProgressUpdater.h index 270292860f..10605db0f4 100644 --- a/libs/widgetutils/KoProgressUpdater.h +++ b/libs/widgetutils/KoProgressUpdater.h @@ -1,140 +1,156 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOPROGRESSUPDATER_H #define KOPROGRESSUPDATER_H #include "kritawidgetutils_export.h" #include #include #include class KoUpdater; class KoProgressProxy; class QTextStream; class QTime; /** * Allow multiple subtasks to safely update and report progress. * This class is able to update a progress bar with the total progress * of a project that may be separated into different subtasks. * Each subtask will use one KoUpdater which that subtask can then report * progress to. Each KoUpdater.setProgress() call will automatically calculate * the total progress over the whole tasks made and update the progress bar * with the total progress so far. * * This class is created specifically with threading in mind so that subtasks * can report their progress from their personal subthread and the progress bar * will be updated correctly and not more often than repaints can occur. * * Typical usage can be: * @code KoProgressUpdater *pu = new KoProgressUpdater(myProgressBar); pu->start(100); // create the subtasks KoUpdater smooth = pu->startSubtask(5); KoUpdater scale = pu->startSubtask(5); KoUpdater cleanup = pu->startSubtask(1); @endcode * Doing a smooth.setProgress(50) will move the progress bar to 50% of the share * of task 'smooth' which is 5 / 11 of the total and thus to 22. * * KoProgressUpdater should be created in the main thread; * KoProgressProxy must be, if it is gui subclass in the QApplication * main thread. The other objects can be created in whatever thread * one wants. * * Also to prevent jumps in the progress-calculation and -display it is recommed * to first create all the subtasks and then start to use setProgress on them. */ class KRITAWIDGETUTILS_EXPORT KoProgressUpdater : public QObject { Q_OBJECT public: enum Mode { Threaded, Unthreaded }; /** * Constructor. * @param progressBar the progress bar to update. */ - explicit KoProgressUpdater(KoProgressProxy *progressBar, Mode mode = Threaded); + explicit KoProgressUpdater(KoProgressProxy *progressProxy, Mode mode = Threaded); + + /** + * @brief a special contructor for connecting the progress updater to a self-destructable + * KoUpdater object. + * + * HACK ALERT: KoUpdater inherits KoProgressProxy, so be careful when constructing + * the updater and check which override is actually used. + */ + explicit KoProgressUpdater(QPointer updater); /// destructor ~KoProgressUpdater() override; /** * Start a new task. * * This will invalidate any previously created subtasks and set * the range of the progressBar as well as the text in the * progressbar. * * @param range the total range of progress bar makes. * @param text The text to show in the progressBar. * @see KoProgressProxy::setRange() * @see KoProgressProxy::setFormat() */ - void start(int range = 100, const QString &text = QLatin1String("%p%")); + void start(int range = 100, const QString &text = ""); /** * After calling start() you can create any number of Updaters, * one for each subtask. @param weight use a weight to specify the * weight this subtask has compared to the rest of the subtasks. * * KoProgressUpdater will delete the KoUpdater instances when a * start() is called or when it is deleted. The KoUpdater pointers * are packed in a QPointer so you can check whether they have * been deleted before dereferencing. */ QPointer startSubtask(int weight=1, - const QString &name = QString()); + const QString &name = QString(), bool isPersistent = false); + + void removePersistentSubtask(QPointer updater); /** * Cancelling the action will make each subtask be marked as 'interrupted' and * set the total progress to 100%. */ void cancel(); /** * @return true when the processing is interrupted */ bool interrupted() const; + void setUpdateInterval(int ms); + int updateInterval() const; + + void setAutoNestNames(bool value); + bool autoNestNames() const; private Q_SLOTS: void update(); void updateUi(); private: class Private; Private *const d; }; #endif diff --git a/libs/widgetutils/KoUpdater.cpp b/libs/widgetutils/KoUpdater.cpp index 009f37ae0a..49c8f1e2f7 100644 --- a/libs/widgetutils/KoUpdater.cpp +++ b/libs/widgetutils/KoUpdater.cpp @@ -1,104 +1,116 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2009 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoUpdater.h" #include "KoUpdaterPrivate_p.h" -KoUpdater::KoUpdater(KoUpdaterPrivate *p) - : QObject(p), - m_progressPercent(0) +KoUpdater::KoUpdater(KoUpdaterPrivate *_d) + : m_progressPercent(0) { - d = p; - Q_ASSERT(p); + d = _d; Q_ASSERT(!d.isNull()); connect(this, SIGNAL(sigCancel()), d, SLOT(cancel())); connect(this, SIGNAL(sigProgress(int)), d, SLOT(setProgress(int))); - connect(d, SIGNAL(sigInterrupted()), this, SLOT(interrupt())); + connect(this, SIGNAL(sigNestedNameChanged(QString)), d, SLOT(setAutoNestedName(QString))); + connect(this, SIGNAL(sigHasValidRangeChanged(bool)), d, SLOT(setHasValidRange(bool))); + connect(d, SIGNAL(sigInterrupted(bool)), this, SLOT(setInterrupted(bool))); + setRange(0, 100); m_interrupted = false; } +KoUpdater::~KoUpdater() +{ +} + void KoUpdater::cancel() { emit sigCancel(); } void KoUpdater::setProgress(int percent) { - if (m_progressPercent >= percent) { - return; - } m_progressPercent = percent; emit sigProgress( percent ); } int KoUpdater::progress() const { return m_progressPercent; } bool KoUpdater::interrupted() const { return m_interrupted; } int KoUpdater::maximum() const { return 100; } void KoUpdater::setValue( int value ) { + value = qBound(min, value, max); - if ( value < min ) value = min; - if ( value > max ) value = max; // Go from range to percent - if (range == 0) return; - setProgress( ((100 * value ) / range) + 1 ); + const int range = max - min; + + if (range == 0) { + m_progressPercent = max; + emit sigProgress(max); + } else { + setProgress((100 * (value - min)) / (max - min)); + } } void KoUpdater::setRange( int minimum, int maximum ) { - min = minimum - 1; + min = minimum; max = maximum; range = max - min; + emit sigHasValidRangeChanged(range != 0); } void KoUpdater::setFormat( const QString & format ) { - Q_UNUSED(format); - // XXX: Do nothing + emit sigNestedNameChanged(format); +} + +void KoUpdater::setAutoNestedName(const QString &name) +{ + emit sigNestedNameChanged(name); } -void KoUpdater::interrupt() +void KoUpdater::setInterrupted(bool value) { m_interrupted = true; } KoDummyUpdater::KoDummyUpdater() : KoUpdater(new KoUpdaterPrivate(0, 0, "dummy")) { } diff --git a/libs/widgetutils/KoUpdater.h b/libs/widgetutils/KoUpdater.h index f4dd208e92..c580ad90da 100644 --- a/libs/widgetutils/KoUpdater.h +++ b/libs/widgetutils/KoUpdater.h @@ -1,129 +1,134 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2009 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_UPDATER_H #define KO_UPDATER_H #include "KoProgressProxy.h" #include #include class KoProgressUpdater; class KoUpdaterPrivate; /** * An KoUpdater is a helper for keeping the progress of each subtask up to speed. * This class is not thread safe, and it should only be used from one thread. * The thread it is used in can be different from any other subtask or the * KoProgressUpdater, though. * * It is possible to create a KoProgressUpdater on a KoUpdater for when you * need to recursively split up progress reporting. (For instance, when your * progress reporting routine can be called by other progress reporting * routines.) * * KoUpdater implements KoProgressProxy because it is possible to recursively * create another KoProgressUpdater with an updater as progress proxy. * * @see KoProgressUpdater::startSubtask() */ class KRITAWIDGETUTILS_EXPORT KoUpdater : public QObject, public KoProgressProxy { Q_OBJECT public: + virtual ~KoUpdater(); /** * Call this when this subtask wants to abort all the actions. */ void cancel(); public Q_SLOTS: /** * Update your progress. Progress is always from 0 to 100. * The global progress shown to the user is determined by the total * amount of subtasks there are. This means that each subtasks can just * report its own private progress in the range from 0 to 100. */ void setProgress(int percent); public: /** * return true when this task should stop processing immediately. * When the task has been cancelled all the subtasks will get interrupted * and should stop working. It is therefor important to have repeated calls to * this method at regular (time) intervals and as soon as the method returns true * cancel the subtask. * @return true when this task should stop processing immediately. */ bool interrupted() const; /** * return the progress this subtask has made. */ int progress() const; public: // KoProgressProxy implementation int maximum() const override; void setValue( int value ) override; void setRange( int minimum, int maximum ) override; void setFormat( const QString & format ) override; + void setAutoNestedName(const QString &name) override; Q_SIGNALS: /// emitted whenever the subtask has called cancel on us void sigCancel(); /// emitted whenever the subtask has called setProgress on us void sigProgress( int percent ); + void sigNestedNameChanged(const QString &value); + void sigHasValidRangeChanged(bool value); + protected: - friend class KoProgressUpdater; - KoUpdater(KoUpdaterPrivate *p); + friend class KoUpdaterPrivate; + KoUpdater(KoUpdaterPrivate *_d); public: QPointer d; int range; int min; int max; private Q_SLOTS: - void interrupt(); + void setInterrupted(bool value); private: bool m_interrupted; int m_progressPercent; }; /// An updater that does nothing class KRITAWIDGETUTILS_EXPORT KoDummyUpdater : public KoUpdater { public: KoDummyUpdater(); }; #endif diff --git a/libs/widgetutils/KoUpdaterPrivate_p.cpp b/libs/widgetutils/KoUpdaterPrivate_p.cpp index defa00380f..4562ab390c 100644 --- a/libs/widgetutils/KoUpdaterPrivate_p.cpp +++ b/libs/widgetutils/KoUpdaterPrivate_p.cpp @@ -1,46 +1,116 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2009 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoUpdaterPrivate_p.h" +#include + +KoUpdaterPrivate::KoUpdaterPrivate(KoProgressUpdater *parent, int weight, const QString &name, bool isPersistent) + : QObject(0) + , m_progress(0) + , m_weight(weight) + , m_interrupted(false) + , m_autoNestedName() + , m_subTaskName(name) + , m_hasValidRange(true) + , m_isPersistent(isPersistent) + , m_parent(parent) + , m_connectedUpdater(new KoUpdater(this)) +{ +} KoUpdaterPrivate::~KoUpdaterPrivate() { - interrupt(); + setInterrupted(true); + m_connectedUpdater->deleteLater(); +} + +QString KoUpdaterPrivate::autoNestedName() const +{ + return m_autoNestedName; +} + +QString KoUpdaterPrivate::subTaskName() const +{ + return m_subTaskName; +} + +QString KoUpdaterPrivate::mergedSubTaskName() const +{ + QString result = m_subTaskName; + + if (!m_autoNestedName.isEmpty()) { + if (result.isEmpty()) { + result = m_autoNestedName; + } else { + result = QString("%1: %2").arg(result).arg(m_autoNestedName); + } + } + + return result; +} + +bool KoUpdaterPrivate::hasValidRange() const +{ + return m_hasValidRange; +} + +bool KoUpdaterPrivate::isPersistent() const +{ + return m_isPersistent; +} + +bool KoUpdaterPrivate::isCompleted() const +{ + return m_progress >= 100; } void KoUpdaterPrivate::cancel() { m_parent->cancel(); } -void KoUpdaterPrivate::interrupt() +void KoUpdaterPrivate::setInterrupted(bool value) { - m_interrupted = true; - emit sigInterrupted(); + m_interrupted = value; + emit sigInterrupted(m_interrupted); } void KoUpdaterPrivate::setProgress(int percent) { - if (m_progress >= percent) { - return; - } m_progress = percent; emit sigUpdated(); } + +void KoUpdaterPrivate::setAutoNestedName(const QString &name) +{ + m_autoNestedName = name; + emit sigUpdated(); +} + +void KoUpdaterPrivate::setHasValidRange(bool value) +{ + m_hasValidRange = value; + emit sigUpdated(); +} + +QPointer KoUpdaterPrivate::connectedUpdater() const +{ + return m_connectedUpdater; +} diff --git a/libs/widgetutils/KoUpdaterPrivate_p.h b/libs/widgetutils/KoUpdaterPrivate_p.h index 5e6bb92f62..bf1769f82c 100644 --- a/libs/widgetutils/KoUpdaterPrivate_p.h +++ b/libs/widgetutils/KoUpdaterPrivate_p.h @@ -1,96 +1,106 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2009 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_UPDATERPRIVATE__P_H #define KO_UPDATERPRIVATE__P_H #include "KoProgressUpdater.h" #include #include /** * KoUpdaterPrivate is the gui-thread side of KoUpdater. Communication * between KoUpdater and KoUpdaterPrivate is handled through queued * connections -- this is the main app thread part of the * KoUpdater-KoUpdaterPrivate bridge. * * The gui thread can iterate over its list of KoUpdaterPrivate * instances for the total progress computation: the queued signals * from the threads will only arrive when the eventloop in the gui * thread has a chance to deliver them. */ class KoUpdaterPrivate : public QObject { Q_OBJECT public: - KoUpdaterPrivate(KoProgressUpdater *parent, int weight, const QString& name) - : QObject(0) - , m_progress(0) - , m_weight(weight) - , m_interrupted(false) - , m_parent(parent) - { - setObjectName(name); - } + KoUpdaterPrivate(KoProgressUpdater *parent, int weight, const QString& name, bool isPersistent = false); /// when deleting an updater, make sure the accompanying thread is /// interrupted, too. ~KoUpdaterPrivate() override; bool interrupted() const { return m_interrupted; } int progress() const { return m_progress; } int weight() const { return m_weight; } + QString autoNestedName() const; + QString subTaskName() const; + QString mergedSubTaskName() const; + + bool hasValidRange() const; + bool isPersistent() const; + bool isCompleted() const; + + QPointer connectedUpdater() const; + public Q_SLOTS: /// Cancel comes from KoUpdater void cancel(); - /// Interrupt comes from the gui, through KoProgressUpdater, goes - /// to KoUpdater to signal running tasks they might as well quit. - void interrupt(); + void setInterrupted(bool value = true); /// progress comes from KoUpdater void setProgress( int percent ); + void setAutoNestedName(const QString &name); + void setHasValidRange(bool value); + + Q_SIGNALS: /// Emitted whenever the progress changed void sigUpdated(); /// Emitted whenever the parent KoProgressUpdater is interrupted, /// for instance through a press on a cancel button - void sigInterrupted(); + void sigInterrupted(bool value); private: int m_progress; // always in percent int m_weight; bool m_interrupted; + QString m_subTaskName; + QString m_autoNestedName; + bool m_hasValidRange; + bool m_isPersistent; + KoProgressUpdater *m_parent; + QPointer m_connectedUpdater; }; #endif diff --git a/libs/widgetutils/tests/CMakeLists.txt b/libs/widgetutils/tests/CMakeLists.txt index 82c339e652..283b00c602 100644 --- a/libs/widgetutils/tests/CMakeLists.txt +++ b/libs/widgetutils/tests/CMakeLists.txt @@ -1,16 +1,24 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) -include_directories( ${CMAKE_SOURCE_DIR}/libs/widgetutils ) +include_directories ( + ${CMAKE_SOURCE_DIR}/libs/widgetutils + ${CMAKE_SOURCE_DIR}/sdk/tests +) include(ECMAddTests) ecm_add_tests( KoPropertiesTest.cpp NAME_PREFIX "libs-widgetutils-" LINK_LIBRARIES kritawidgetutils Qt5::Test ) # FIXME this test should be in the ui directory ecm_add_test( kis_simple_math_parser_test.cpp TEST_NAME krita-ui-KisSimpleMathParserTest LINK_LIBRARIES kritaui Qt5::Test) + +ecm_add_test( + TestKoProgressUpdater.cpp + TEST_NAME TestKoProgressUpdater + LINK_LIBRARIES kritaui Qt5::Test) diff --git a/libs/widgetutils/tests/TestKoProgressUpdater.cpp b/libs/widgetutils/tests/TestKoProgressUpdater.cpp new file mode 100644 index 0000000000..35ccda2ba5 --- /dev/null +++ b/libs/widgetutils/tests/TestKoProgressUpdater.cpp @@ -0,0 +1,511 @@ +/* + * 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 "TestKoProgressUpdater.h" + +#include + +#include +#include +#include + + +void TestKoProgressUpdater::test() +{ + TestUtil::TestProgressBar testProxy; + + KoProgressUpdater progress(&testProxy); + progress.setUpdateInterval(1); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("")); + + progress.start(100, "Test Action: %p%"); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + + QPointer updater1 = progress.startSubtask(1, ""); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + + updater1->setProgress(50); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 50); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + + QPointer updater2 = progress.startSubtask(4, ""); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 10); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + + updater2->setProgress(25); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 30); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + + updater1->setProgress(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 40); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + + updater2->setProgress(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 99); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + + // both updaters are auto-killed right after completion + QVERIFY(!updater1); + QVERIFY(!updater2); + +} + +void TestKoProgressUpdater::testNamedSubtasks() +{ + TestUtil::TestProgressBar testProxy; + + KoProgressUpdater progress(&testProxy); + progress.setUpdateInterval(1); + progress.setAutoNestNames(true); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + progress.start(100, "Test Action"); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("Test Action")); + + QPointer updater1 = progress.startSubtask(1, "subtask1"); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("Test Action: subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("Test Action: subtask1")); + + updater1->setProgress(50); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 50); + QCOMPARE(testProxy.format(), QString("Test Action: subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("Test Action: subtask1")); + + QPointer updater2 = progress.startSubtask(4, "subtask2"); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 10); + QCOMPARE(testProxy.format(), QString("Test Action: subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("Test Action: subtask1")); + + updater1->setProgress(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 20); + QCOMPARE(testProxy.format(), QString("Test Action: subtask2: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("Test Action: subtask2")); + + // tests subtask with an empty name! + QPointer updater3 = progress.startSubtask(1, ""); + updater2->setProgress(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 83); + QCOMPARE(testProxy.format(), QString("Test Action: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("Test Action")); +} + +void TestKoProgressUpdater::testNamedSubtasksUnnamedParent() +{ + TestUtil::TestProgressBar testProxy; + + KoProgressUpdater progress(&testProxy); + progress.setUpdateInterval(1); + progress.setAutoNestNames(true); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + progress.start(100, ""); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("%p%")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + QPointer updater1 = progress.startSubtask(1, "subtask1"); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); +} + +void TestKoProgressUpdater::testPersistentSubtask() +{ + TestUtil::TestProgressBar testProxy; + + KoProgressUpdater progress(&testProxy); + progress.setUpdateInterval(1); + progress.setAutoNestNames(true); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + progress.start(100, ""); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("%p%")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + QPointer updater1 = progress.startSubtask(1, "subtask1"); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + QPointer updater2_persistent = progress.startSubtask(1, "subtask2", true); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + updater1->setProgress(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 50); + QCOMPARE(testProxy.format(), QString("subtask2: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask2")); + + updater2_persistent->setValue(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 99); + QCOMPARE(testProxy.format(), QString("%p%")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + // normal subtask is killed, persistent is still alive + QVERIFY(!updater1); + QVERIFY(updater2_persistent); + + progress.removePersistentSubtask(updater2_persistent); + QTest::qWait(15); + + // persistent subtask is killed only after explicit removal + QVERIFY(!updater2_persistent); +} + +void TestKoProgressUpdater::testDestructionNonpersistentSubtasks() +{ + TestUtil::TestProgressBar testProxy; + + KoProgressUpdater progress(&testProxy); + progress.setUpdateInterval(1); + progress.setAutoNestNames(true); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + progress.start(100, ""); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("%p%")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + QPointer updater1 = progress.startSubtask(1, "subtask1"); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + + /// This test checks a weird effect. We can create a progress updater + /// on some other KoUpdater, which might be non-persistent, that is be + /// removed right after the completion. + /// + /// HACK ALERT: + /// We use a hacky approach, we just crete a custom constructor for the + /// progress updater that connects to an updater using QPointer interface. + /// Ideally, we should just make KoProgressProxy inherit QObject, but that + /// would introduce virtual inheritance in KoProgressBar, which we would like + /// to avoid. + + // create a progress updater over a self-destructing updater + KoProgressUpdater subtaskProgress(updater1); + subtaskProgress.setUpdateInterval(1); + subtaskProgress.setAutoNestNames(true); + + subtaskProgress.start(100, ""); + QTest::qWait(15); + + // nothing changed! + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + QPointer subUpdater1 = subtaskProgress.startSubtask(); + QTest::qWait(15); + + // nothing changed! + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + QPointer subUpdater2 = subtaskProgress.startSubtask(); + QTest::qWait(15); + + // nothing changed! + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + subUpdater1->setProgress(50); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 25); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + + subUpdater1->setProgress(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 50); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + subUpdater2->setProgress(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 99); + QCOMPARE(testProxy.format(), QString("%p%")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + // main updater has been killed already since all the children + // updaters are completed + QVERIFY(!updater1); + + // subupdater are killed as well + QVERIFY(!subUpdater1); + QVERIFY(!subUpdater2); +} + +void TestKoProgressUpdater::testUndefinedStateTasks() +{ + TestUtil::TestProgressBar testProxy; + + KoProgressUpdater progress(&testProxy); + progress.setUpdateInterval(1); + progress.setAutoNestNames(true); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + progress.start(100, ""); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("%p%")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + QPointer updater1 = progress.startSubtask(1, "subtask1"); + updater1->setProgress(50); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 50); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + QPointer updater2 = progress.startSubtask(1, "subtask2"); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 25); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + updater2->setRange(0,0); + updater2->setValue(0); + QTest::qWait(15); + + // now we are in undefined state without showing percents! + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask1")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + + updater1->setProgress(100); + QTest::qWait(15); + + // still undefined + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("subtask2")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask2")); + + updater2->setRange(0,100); + updater2->setValue(0); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 99); + QCOMPARE(testProxy.value(), 50); + QCOMPARE(testProxy.format(), QString("subtask2: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask2")); +} + +void TestKoProgressUpdater::testNonStandardRange() +{ + TestUtil::TestProgressBar testProxy; + + KoProgressUpdater progress(&testProxy); + progress.setUpdateInterval(1); + progress.setAutoNestNames(true); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 0); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + progress.start(50, ""); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 49); + QCOMPARE(testProxy.value(), 0); + QCOMPARE(testProxy.format(), QString("%p%")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + QPointer updater1 = progress.startSubtask(1, "subtask1"); + updater1->setProgress(50); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 49); + QCOMPARE(testProxy.value(), 25); + QCOMPARE(testProxy.format(), QString("subtask1: %p%")); + QCOMPARE(testProxy.autoNestedName(), QString("subtask1")); + + updater1->setProgress(100); + QTest::qWait(15); + + QCOMPARE(testProxy.min(), 0); + QCOMPARE(testProxy.max(), 49); + QCOMPARE(testProxy.value(), 49); + QCOMPARE(testProxy.format(), QString("%p%")); + QCOMPARE(testProxy.autoNestedName(), QString("")); + + QVERIFY(!updater1); +} + + +QTEST_MAIN(TestKoProgressUpdater) diff --git a/libs/image/kis_name_server.h b/libs/widgetutils/tests/TestKoProgressUpdater.h similarity index 60% copy from libs/image/kis_name_server.h copy to libs/widgetutils/tests/TestKoProgressUpdater.h index aed1e92d63..1097e86ff1 100644 --- a/libs/image/kis_name_server.h +++ b/libs/widgetutils/tests/TestKoProgressUpdater.h @@ -1,39 +1,40 @@ /* - * Copyright (c) 2002 Patrick Julien + * 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 KIS_NAMESERVER_H_ -#define KIS_NAMESERVER_H_ -#include -#include +#ifndef TESTKOPROGRESSUPDATER_H +#define TESTKOPROGRESSUPDATER_H -class KRITAIMAGE_EXPORT KisNameServer +#include + +class TestKoProgressUpdater : public QObject { -public: - KisNameServer(qint32 seed = 1); - ~KisNameServer(); + Q_OBJECT - qint32 number(); - qint32 currentSeed() const; - void rollback(); +private Q_SLOTS: + void test(); + void testNamedSubtasks(); + void testNamedSubtasksUnnamedParent(); + void testPersistentSubtask(); -private: - qint32 m_generator; -}; + void testDestructionNonpersistentSubtasks(); + void testUndefinedStateTasks(); -#endif // KIS_NAMESERVER_H_ + void testNonStandardRange(); +}; +#endif // TESTKOPROGRESSUPDATER_H diff --git a/packaging/windows/package_2.cmd b/packaging/windows/package_2.cmd index 5cbf0da2f4..14677f10ba 100644 --- a/packaging/windows/package_2.cmd +++ b/packaging/windows/package_2.cmd @@ -1,331 +1,357 @@ @echo off :: This batch script is meant to prepare a Krita package folder to be zipped or :: to be a base for the installer. :: :: Just drop it next to the "i" install folder where the dependencies and Krita :: binaries are. :: :: Also copy filelist_bin_dll.txt and filelist_lib_dll.txt if you want more :: fine-grained DLL dependencies copying. :: :: You may want to review the following parameters. :: :: Configuration parameters: :: :: MINGW_GCC_BIN: Path to the mingw-w64/bin dir :: SEVENZIP_EXE: Path to 7-Zip executable (either 7z.exe or 7zG.exe) :: BUILDDIR_SRC: Path to krita source dir (for package shortcut) :: BUILDDIR_INSTALL: Path to INSTALL prefix of the build :: :: Note that paths should only contain alphanumeric, _ and -, except for the :: path to 7-Zip, which is fine if quoted. :: set MINGW_GCC_BIN=C:\TDM-GCC-64\bin\ set SEVENZIP_EXE="C:\Program Files\7-Zip\7z.exe" rem set BUILDDIR_SRC=%CD%\krita set BUILDDIR_SRC=%CD%\krita set BUILDDIR_INSTALL=%CD%\i :: -------- set PATH=%MINGW_GCC_BIN%;%PATH% echo Krita Windows packaging script echo. echo Configurations: echo MINGW_GCC_BIN: %MINGW_GCC_BIN% echo SEVENZIP_EXE: %SEVENZIP_EXE% echo BUILDDIR_SRC: %BUILDDIR_SRC% echo BUILDDIR_INSTALL: %BUILDDIR_INSTALL% echo. if "%1" == "" ( set PAUSE=pause ) else ( set "PAUSE= " ) :: Simple checking if not exist %BUILDDIR_INSTALL% ( echo ERROR: Cannot find the install folder! %PAUSE% exit /B 1 ) if not "%BUILDDIR_INSTALL: =%" == "%BUILDDIR_INSTALL%" ( echo ERROR: Install path contains space, which will not work properly! %PAUSE% exit /B 1 ) :: Decide package name to use if "%1" == "" ( echo Please input a package name. It will be used for the output directory, package echo file name and as the top-level directory of the package. echo Alternatively, you can pass it as the first argument to this script. echo You should only use alphanumeric, _ and - echo. set /P pkg_name=Package name^> setlocal if "!pkg_name!" == "" ( echo ERROR: You cannot choose an empty name! %PAUSE% exit /B 1 ) endlocal ) else ( set pkg_name=%1 ) echo Package name is "%pkg_name%" set pkg_root=%CD%\%pkg_name% echo Packaging dir is %pkg_root% echo. if exist %pkg_root% ( echo ERROR: Packaging dir already exists! Please remove or rename it first. %PAUSE% exit /B 1 ) echo. echo Trying to guess GCC version... g++ --version > NUL if errorlevel 1 ( echo ERROR: g++ is not working. %PAUSE% exit /B 1 ) for /f "delims=" %%a in ('g++ --version ^| find "g++"') do set GCC_VERSION_LINE=%%a echo -- %GCC_VERSION_LINE% if "%GCC_VERSION_LINE:tdm64=%" == "%GCC_VERSION_LINE%" ( echo Compiler doesn't look like TDM64-GCC, assuming simple mingw-w64 set IS_TDM= ) else ( echo Compiler looks like TDM64-GCC set IS_TDM=1 ) echo. echo Trying to guess target architecture... objdump --version > NUL if errorlevel 1 ( echo ERROR: objdump is not working. %PAUSE% exit /B 1 ) for /f "delims=, tokens=1" %%a in ('objdump -f %BUILDDIR_INSTALL%\bin\krita.exe ^| find "architecture"') do set TARGET_ARCH_LINE=%%a echo -- %TARGET_ARCH_LINE% if "%TARGET_ARCH_LINE:x86-64=%" == "%TARGET_ARCH_LINE%" ( echo Target looks like x86 set IS_X64= ) else ( echo Target looks like x64 set IS_x64=1 ) echo. echo Testing for objcopy... objcopy --version > NUL if errorlevel 1 ( echo ERROR: objcopy is not working. %PAUSE% exit /B 1 ) echo. echo Testing for strip... strip --version > NUL if errorlevel 1 ( echo ERROR: strip is not working. %PAUSE% exit /B 1 ) echo. echo Creating base directories... mkdir %pkg_root% && ^ mkdir %pkg_root%\bin && ^ mkdir %pkg_root%\lib && ^ mkdir %pkg_root%\share if errorlevel 1 ( echo ERROR: Cannot create packaging dir tree! %PAUSE% exit /B 1 ) echo. echo Copying GCC libraries... if x%IS_TDM% == x ( if x%is_x64% == x ( :: mingw-w64 x86 set "STDLIBS=gcc_s_dw2-1 gomp-1 stdc++-6 winpthread-1" ) else ( :: mingw-w64 x64 set "STDLIBS=gcc_s_seh-1 gomp-1 stdc++-6 winpthread-1" ) ) else ( if x%is_x64% == x ( :: TDM-GCC x86 set "STDLIBS=gomp-1" ) else ( :: TDM-GCC x64 set "STDLIBS=gomp_64-1" ) ) for %%L in (%STDLIBS%) do copy "%MINGW_GCC_BIN%\lib%%L.dll" %pkg_root%\bin echo. echo Copying files... :: krita.exe copy %BUILDDIR_INSTALL%\bin\krita.exe %pkg_root%\bin :: DLLs from bin/ if exist filelist_bin_dll.txt ( for /f %%F in (filelist_bin_dll.txt) do copy %BUILDDIR_INSTALL%\bin\%%F %pkg_root%\bin ) else ( echo INFO: filelist_bin_dll.txt not found, copying all DLLs except Qt5 from bin/ setlocal enableextensions enabledelayedexpansion for /f "delims=" %%F in ('dir /b "%BUILDDIR_INSTALL%\bin\*.dll"') do ( set file=%%F set file=!file:~0,3! if not x!file! == xQt5 copy %BUILDDIR_INSTALL%\bin\%%F %pkg_root%\bin ) endlocal ) :: symsrv.yes for Dr. Mingw copy %BUILDDIR_INSTALL%\bin\symsrv.yes %pkg_root%\bin :: DLLs from lib/ if exist filelist_lib_dll.txt ( for /f %%F in (filelist_lib_dll.txt) do copy %BUILDDIR_INSTALL%\lib\%%F %pkg_root%\bin ) else ( echo INFO: filelist_lib_dll.txt not found, copying all DLLs from lib/ copy %BUILDDIR_INSTALL%\lib\*.dll %pkg_root%\bin ) :: Boost, there might be more than one leftover but we can't really do much copy %BUILDDIR_INSTALL%\bin\libboost_system-*.dll %pkg_root%\bin :: KF5 plugins may be placed at different locations depending on how Qt is built xcopy /S /Y /I %BUILDDIR_INSTALL%\lib\plugins\imageformats %pkg_root%\bin\imageformats xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\imageformats %pkg_root%\bin\imageformats xcopy /S /Y /I %BUILDDIR_INSTALL%\lib\plugins\kf5 %pkg_root%\bin\kf5 xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\kf5 %pkg_root%\bin\kf5 :: Qt Translations :: it seems that windeployqt does these, but only *some* of these??? mkdir %pkg_root%\bin\translations setlocal enableextensions enabledelayedexpansion for /f "delims=" %%F in ('dir /b "%BUILDDIR_INSTALL%\translations\qt_*.qm"') do ( :: Exclude qt_help_*.qm set temp=%%F set temp2=!temp:_help=! if x!temp2! == x!temp! copy %BUILDDIR_INSTALL%\translations\!temp! %pkg_root%\bin\translations\!temp! ) endlocal :: Krita plugins xcopy /Y %BUILDDIR_INSTALL%\lib\kritaplugins\*.dll %pkg_root%\lib\kritaplugins\ :: Share xcopy /Y /S /I %BUILDDIR_INSTALL%\share\color %pkg_root%\share\color xcopy /Y /S /I %BUILDDIR_INSTALL%\share\color-schemes %pkg_root%\share\color-schemes xcopy /Y /S /I %BUILDDIR_INSTALL%\share\icons %pkg_root%\share\icons xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kf5 %pkg_root%\share\kf5 xcopy /Y /S /I %BUILDDIR_INSTALL%\share\krita %pkg_root%\share\krita xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kritaplugins %pkg_root%\share\kritaplugins xcopy /Y /S /I %BUILDDIR_INSTALL%\share\mime %pkg_root%\share\mime :: Not useful on Windows it seems rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\appdata %pkg_root%\share\appdata rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\applications %pkg_root%\share\applications rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\doc %pkg_root%\share\doc rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kservices5 %pkg_root%\share\kservices5 rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\man %pkg_root%\share\man rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\ocio %pkg_root%\share\ocio :: Copy locale to bin xcopy /Y /S /I %BUILDDIR_INSTALL%\share\locale %pkg_root%\bin\locale :: Copy shortcut link from source (can't create it dynamically) copy %BUILDDIR_SRC%\packaging\windows\krita.lnk %pkg_root% :: windeployqt %BUILDDIR_INSTALL%\bin\windeployqt.exe --release -concurrent -network -printsupport -svg -xml -multimedia %pkg_root%\bin\krita.exe +:: Copy embedded Python +xcopy /Y /S /I %BUILDDIR_INSTALL%\python %pkg_root%\python + :: For chopping relative path :: 512 should be enough :: n+2 to also account for a trailing backslash setlocal enableextensions enabledelayedexpansion for /L %%n in (1 1 512) do if "!pkg_root:~%%n,1!" neq "" set /a "pkg_root_len_plus_one=%%n+2" endlocal & set pkg_root_len_plus_one=%pkg_root_len_plus_one% +echo. +setlocal enableextensions enabledelayedexpansion +:: Remove Python cache files +for /d /r "%pkg_root%" %%F in (__pycache__\) do ( + if EXIST "%%F" ( + set relpath=%%F + set relpath=!relpath:~%pkg_root_len_plus_one%! + echo Deleting Python cache !relpath! + rmdir /S /Q "%%F" + ) +) +endlocal + echo. echo Splitting debug info from binaries... call :split-debug "%pkg_root%\bin\krita.exe" bin\krita.exe setlocal enableextensions enabledelayedexpansion :: Find all DLLs for /r "%pkg_root%" %%F in (*.dll) do ( set relpath=%%F set relpath=!relpath:~%pkg_root_len_plus_one%! call :split-debug "%%F" !relpath! ) endlocal +setlocal enableextensions enabledelayedexpansion +:: Find all Python native modules +for /r "%pkg_root%\share\krita\pykrita\" %%F in (*.pyd) do ( + set relpath=%%F + set relpath=!relpath:~%pkg_root_len_plus_one%! + call :split-debug "%%F" !relpath! +) +endlocal echo. echo Packaging debug info... :: (note that the top-level package dir is not included) %SEVENZIP_EXE% a -tzip %pkg_name%-dbg.zip -r %pkg_root%\*.debug echo -------- echo. echo Packaging stripped binaries... %SEVENZIP_EXE% a -tzip %pkg_name%.zip %pkg_root%\ -xr!.debug echo -------- echo. echo. echo Krita packaged as %pkg_name%.zip if exist %pkg_name%-dbg.zip echo Debug info packaged as %pkg_name%-dbg.zip echo Packaging dir is %pkg_root% -echo NOTE: Do not create installer with packaging dir unless you removed all debug -echo info from it! +echo NOTE: Do not create installer with packaging dir. Extract from +echo %pkg_name%.zip instead, +echo and do _not_ run krita inside the extracted directory because it will +echo create extra unnecessary files. echo. echo Please remember to actually test the package before releasing it. echo. %PAUSE% exit /b :split-debug echo Splitting debug info of %2 objcopy --only-keep-debug %~1 %~1.debug if ERRORLEVEL 1 exit /b %ERRORLEVEL% :: If the debug file is small enough then consider there being no debug info. :: Discard these files since they somehow make gdb crash. call :getfilesize %~1.debug if /i %getfilesize_retval% LEQ 2048 ( echo Discarding %2.debug del %~1.debug exit /b 0 ) if not exist %~dp1.debug mkdir %~dp1.debug move %~1.debug %~dp1.debug\ > NUL strip %~1 :: Add debuglink :: FIXME: There is a problem with gdb that cause it to output this warning :: FIXME: "warning: section .gnu_debuglink not found in xxx.debug" :: FIXME: I tried adding a link to itself but this kills drmingw :( objcopy --add-gnu-debuglink="%~dp1.debug\%~nx1.debug" %~1 exit /b %ERRORLEVEL% :getfilesize set getfilesize_retval=%~z1 goto :eof :relpath_dirpath call :relpath_dirpath_internal "" "%~1" goto :eof :relpath_dirpath_internal for /f "tokens=1* delims=\" %%a in ("%~2") do ( :: If part 2 is empty, it means part 1 is probably the file name if x%%b==x ( set relpath_dirpath_retval=%~1 ) else ( call :relpath_dirpath_internal "%~1%%a\" %%b ) ) goto :eof diff --git a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp index cc20c8eaee..25b24757c2 100644 --- a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp +++ b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp @@ -1,304 +1,304 @@ /* * Copyright (c) 2012 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; 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 "compositiondocker_dock.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 "compositionmodel.h" CompositionDockerDock::CompositionDockerDock( ) : QDockWidget(i18n("Compositions")), m_canvas(0) { QWidget* widget = new QWidget(this); setupUi(widget); m_model = new CompositionModel(this); compositionView->setModel(m_model); compositionView->installEventFilter(this); deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete")); saveButton->setIcon(KisIconUtils::loadIcon("list-add")); exportButton->setIcon(KisIconUtils::loadIcon("document-export")); deleteButton->setToolTip(i18n("Delete Composition")); saveButton->setToolTip(i18n("New Composition")); exportButton->setToolTip(i18n("Export Composition")); setWidget(widget); connect( compositionView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(activated ( const QModelIndex & ) ) ); compositionView->setContextMenuPolicy(Qt::CustomContextMenu); connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint))); connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked())); connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked())); connect( exportButton, SIGNAL(clicked(bool)), this, SLOT(exportClicked())); saveNameEdit->setPlaceholderText(i18n("Insert Name")); } CompositionDockerDock::~CompositionDockerDock() { } void CompositionDockerDock::setCanvas(KoCanvasBase * canvas) { if (m_canvas && m_canvas->viewManager()) { Q_FOREACH (KisAction *action, m_actions) { m_canvas->viewManager()->actionManager()->takeAction(action); } } unsetCanvas(); setEnabled(canvas != 0); m_canvas = dynamic_cast(canvas); if (m_canvas && m_canvas->viewManager()) { if (m_actions.isEmpty()) { KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition"); connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition())); m_actions.append(updateAction); KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition"); connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition())); m_actions.append(renameAction); } else { Q_FOREACH (KisAction *action, m_actions) { m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action); } } updateModel(); } } void CompositionDockerDock::unsetCanvas() { setEnabled(false); m_canvas = 0; m_model->setCompositions(QList()); } void CompositionDockerDock::activated(const QModelIndex& index) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); composition->apply(); } void CompositionDockerDock::deleteClicked() { QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); m_canvas->viewManager()->image()->removeComposition(composition); updateModel(); } } void CompositionDockerDock::saveClicked() { KisImageWSP image = m_canvas->viewManager()->image(); if (!image) return; // format as 001, 002 ... QString name = saveNameEdit->text(); if (name.isEmpty()) { bool found = false; int i = 1; do { name = QString("%1").arg(i, 3, 10, QChar('0')); found = false; Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) { if (composition->name() == name) { found = true; break; } } i++; } while(found && i < 1000); } KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->store(); image->addComposition(composition); saveNameEdit->clear(); updateModel(); compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0)); image->setModified(); } void CompositionDockerDock::updateModel() { if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) { m_model->setCompositions(m_canvas->viewManager()->image()->compositions()); } } void CompositionDockerDock::exportClicked() { if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) { QString path; KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock"); dialog.setCaption(i18n("Select a Directory")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); path = dialog.filename(); if (path.isNull()) return; if (!path.endsWith('/')) { path.append('/'); } KisImageWSP image = m_canvas->viewManager()->image(); QString filename = m_canvas->viewManager()->document()->localFilePath(); if (!filename.isEmpty()) { QFileInfo info(filename); path += info.baseName() + '_'; } Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) { if (!composition->isExportEnabled()) { continue; } composition->apply(); image->refreshGraph(); image->lock(); #if 0 image->rootLayer()->projection()->convertToQImage(0, 0, 0, image->width(), image->height()).save(path + composition->name() + ".png"); #else QRect r = image->bounds(); KisDocument *d = KisPart::instance()->createDocument(); KisImageWSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name()); dst->setResolution(image->xRes(), image->yRes()); d->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8); KisPainter gc(paintLayer->paintDevice()); gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->refreshGraph(); - d->setOutputMimeType("image/png"); d->setFileBatchMode(true); - d->exportDocument(QUrl::fromLocalFile(path + composition->name() + ".png")); + const QByteArray outputFormat("image/png"); + d->exportDocumentSync(QUrl::fromLocalFile(path + composition->name() + ".png"), outputFormat); delete d; #endif image->unlock(); } } } bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress ) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) { // new index will be set after the method is called QTimer::singleShot(0, this, SLOT(activateCurrentIndex())); } return false; } else { return QObject::eventFilter(obj, event); } } void CompositionDockerDock::activateCurrentIndex() { QModelIndex index = compositionView->currentIndex(); if (index.isValid()) { activated(index); } } void CompositionDockerDock::customContextMenuRequested(QPoint pos) { if (m_actions.isEmpty()) return; QMenu menu; Q_FOREACH (KisAction *action, m_actions) { menu.addAction(action); } menu.exec(compositionView->mapToGlobal(pos)); } void CompositionDockerDock::updateComposition() { QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); composition->store(); m_canvas->image()->setModified(); } } void CompositionDockerDock::renameComposition() { dbgKrita << "rename"; QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); bool ok; QString name = QInputDialog::getText(this, i18n("Rename Composition"), i18n("New Name:"), QLineEdit::Normal, composition->name(), &ok); if (ok && !name.isEmpty()) { composition->setName(name); m_canvas->image()->setModified(); } } } diff --git a/plugins/dockers/defaultdockers/kis_layer_box.cpp b/plugins/dockers/defaultdockers/kis_layer_box.cpp index 1c45264ba5..84da5f255e 100644 --- a/plugins/dockers/defaultdockers/kis_layer_box.cpp +++ b/plugins/dockers/defaultdockers/kis_layer_box.cpp @@ -1,929 +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"); + QMenu *locksMenu = menu.addMenu(i18n("&Toggle Locks && Visibility")); addActionToMenu(locksMenu, "toggle_layer_visibility"); - addActionToMenu(locksMenu, "toggle_layer_alpha_lock"); + addActionToMenu(locksMenu, "toggle_layer_lock"); addActionToMenu(locksMenu, "toggle_layer_inherit_alpha"); + addActionToMenu(locksMenu, "toggle_layer_alpha_lock"); 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/imagedocker/imagedocker_dock.cpp b/plugins/dockers/imagedocker/imagedocker_dock.cpp index eebaceebf9..fd846ff51a 100644 --- a/plugins/dockers/imagedocker/imagedocker_dock.cpp +++ b/plugins/dockers/imagedocker/imagedocker_dock.cpp @@ -1,564 +1,593 @@ /* * 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; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include +#include +#include + #include "imagedocker_dock.h" #include "image_strip_scene.h" #include "image_view.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgimagedocker.h" #include "ui_wdgImageViewPopup.h" /////////////////////////////////////////////////////////////////////////////// // --------- ImageFilter --------------------------------------------------- // class ImageFilter: public QSortFilterProxyModel { bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override { QFileSystemModel* model = static_cast(sourceModel()); QModelIndex index = sourceModel()->index(source_row, 0, source_parent); if(model->isDir(index)) return true; QString ext = model->fileInfo(index).suffix().toLower(); if(s_supportedImageFormats.isEmpty()) { s_supportedImageFormats = QImageReader::supportedImageFormats(); } //QImageReader::supportedImageFormats return a list with mixed-case ByteArrays so //iterate over it manually to make it possible to do toLower(). Q_FOREACH (const QByteArray& format, s_supportedImageFormats) { if(format.toLower() == ext.toUtf8()) { return true; } } return false; } bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const override { Q_UNUSED(source_parent); return source_column == 0; } static QList s_supportedImageFormats; }; QList ImageFilter::s_supportedImageFormats; /////////////////////////////////////////////////////////////////////////////// // --------- ImageListModel ------------------------------------------------ // class ImageListModel: public QAbstractListModel { struct Data { QPixmap icon; QString text; qint64 id; }; public: void addImage(const QPixmap& pixmap, const QString& text, qint64 id) { Data data; data.icon = pixmap.scaled(70, 70, Qt::KeepAspectRatio, Qt::SmoothTransformation); data.text = text; data.id = id; emit layoutAboutToBeChanged(); m_data.push_back(data); emit layoutChanged(); } qint64 imageID(int index) const { return m_data[index].id; } void removeImage(qint64 id) { typedef QList::iterator Iterator; for(Iterator data=m_data.begin(); data!=m_data.end(); ++data) { if(data->id == id) { emit layoutAboutToBeChanged(); m_data.erase(data); emit layoutChanged(); return; } } } int indexFromID(qint64 id) { for(int i=0; i m_data; }; /////////////////////////////////////////////////////////////////////////////// // --------- ImageDockerUI ------------------------------------------------- // struct ImageDockerUI: public QWidget, public Ui_wdgImageDocker { ImageDockerUI() { setupUi(this); } }; /////////////////////////////////////////////////////////////////////////////// // --------- PopupWidgetUI ------------------------------------------------- // struct PopupWidgetUI: public QWidget, public Ui_wdgImageViewPopup { PopupWidgetUI() { setupUi(this); } }; /////////////////////////////////////////////////////////////////////////////// // --------- ImageDockerDock ----------------------------------------------- // ImageDockerDock::ImageDockerDock(): QDockWidget(i18n("Reference Images")), m_canvas(0), m_currImageID(-1) { m_ui = new ImageDockerUI(); m_popupUi = new PopupWidgetUI(); m_zoomButtons = new QButtonGroup(); m_imgListModel = new ImageListModel(); m_imageStripScene = new ImageStripScene(); m_model = new QFileSystemModel(); m_proxyModel = new ImageFilter(); m_proxyModel->setSourceModel(m_model); m_proxyModel->setDynamicSortFilter(true); m_ui->bnBack->setIcon(KisIconUtils::loadIcon("arrow-left")); m_ui->bnUp->setIcon(KisIconUtils::loadIcon("arrow-up")); m_ui->bnHome->setIcon(KisIconUtils::loadIcon("go-home")); m_ui->bnImgPrev->setIcon(KisIconUtils::loadIcon("arrow-left")); m_ui->bnImgNext->setIcon(KisIconUtils::loadIcon("arrow-right")); m_ui->bnImgClose->setIcon(KisIconUtils::loadIcon("window-close")); m_ui->thumbView->setScene(m_imageStripScene); m_ui->treeView->setModel(m_proxyModel); m_ui->cmbImg->setModel(m_imgListModel); m_ui->bnPopup->setIcon(KisIconUtils::loadIcon("zoom-original")); m_ui->bnPopup->setPopupWidget(m_popupUi); m_popupUi->zoomSlider->setRange(5, 500); m_popupUi->zoomSlider->setValue(100); m_zoomButtons->addButton(m_popupUi->bnZoomFit , ImageView::VIEW_MODE_FIT); m_zoomButtons->addButton(m_popupUi->bnZoomAdjust, ImageView::VIEW_MODE_ADJUST); m_zoomButtons->addButton(m_popupUi->bnZoom25 , 25); m_zoomButtons->addButton(m_popupUi->bnZoom50 , 50); m_zoomButtons->addButton(m_popupUi->bnZoom75 , 75); m_zoomButtons->addButton(m_popupUi->bnZoom100 , 100); installEventFilter(this); m_ui->cmbPath->addItem(KisIconUtils::loadIcon("folder-pictures"), QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); m_ui->cmbPath->addItem(KisIconUtils::loadIcon("folder-documents"), QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation)); m_ui->cmbPath->addItem(KisIconUtils::loadIcon("go-home"), QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); Q_FOREACH (const QFileInfo &info, QDir::drives()) { m_ui->cmbPath->addItem(KisIconUtils::loadIcon("drive-harddisk"), info.absolutePath()); } connect(m_ui->cmbPath, SIGNAL(activated(const QString&)), SLOT(slotChangeRoot(const QString&))); - m_model->setRootPath(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); - m_ui->treeView->setRootIndex(m_proxyModel->mapFromSource(m_model->index(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)))); + loadConfigState(); connect(m_ui->treeView , SIGNAL(doubleClicked(const QModelIndex&)) , SLOT(slotItemDoubleClicked(const QModelIndex&))); connect(m_ui->bnBack , SIGNAL(clicked(bool)) , SLOT(slotBackButtonClicked())); connect(m_ui->bnHome , SIGNAL(clicked(bool)) , SLOT(slotHomeButtonClicked())); connect(m_ui->bnUp , SIGNAL(clicked(bool)) , SLOT(slotUpButtonClicked())); connect(m_imageStripScene , SIGNAL(sigImageActivated(const QString&)) , SLOT(slotOpenImage(QString))); connect(m_ui->bnImgNext , SIGNAL(clicked(bool)) , SLOT(slotNextImage())); connect(m_ui->bnImgPrev , SIGNAL(clicked(bool)) , SLOT(slotPrevImage())); connect(m_ui->bnImgClose , SIGNAL(clicked(bool)) , SLOT(slotCloseCurrentImage())); connect(m_ui->cmbImg , SIGNAL(activated(int)) , SLOT(slotImageChoosenFromComboBox(int))); connect(m_ui->imgView , SIGNAL(sigColorSelected(const QColor&)) , SLOT(slotColorSelected(const QColor))); connect(m_ui->imgView , SIGNAL(sigViewModeChanged(int, qreal)) , SLOT(slotViewModeChanged(int, qreal))); connect(m_popupUi->zoomSlider , SIGNAL(valueChanged(int)) , SLOT(slotZoomChanged(int))); connect(m_zoomButtons , SIGNAL(buttonClicked(int)) , SLOT(slotZoomChanged(int))); connect(m_zoomButtons , SIGNAL(buttonClicked(int)) , SLOT(slotCloseZoomPopup())); setWidget(m_ui); setAcceptDrops(true); } ImageDockerDock::~ImageDockerDock() { + saveConfigState(); + delete m_proxyModel; delete m_model; delete m_imageStripScene; delete m_imgListModel; delete m_zoomButtons; qDeleteAll(m_temporaryFiles); } void ImageDockerDock::dragEnterEvent(QDragEnterEvent *event) { event->setAccepted(event->mimeData()->hasImage() || event->mimeData()->hasUrls()); } void ImageDockerDock::dropEvent(QDropEvent *event) { QImage image; if (event->mimeData()->hasImage()) { image = qvariant_cast(event->mimeData()->imageData()); } if (!image.isNull()) { QTemporaryFile *file = new QTemporaryFile(QDir::tempPath () + QDir::separator() + "krita_reference_dnd_XXXXXX.png"); m_temporaryFiles.append(file); file->open(); image.save(file, "PNG"); file->close(); slotOpenImage(file->fileName()); } else if (event->mimeData()->hasUrls()) { QList urls = event->mimeData()->urls(); Q_FOREACH (const QUrl &url, urls) { QString path = url.path(); QFileInfo info(path); if (info.exists() && !QImageReader::imageFormat(path).isEmpty()) { slotOpenImage(path); } } } } void ImageDockerDock::showEvent(QShowEvent *) { if (m_imageStripScene->currentPath().isNull()) { updatePath(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); } } void ImageDockerDock::setCanvas(KoCanvasBase* canvas) { // Intentionally not disabled if there's no canvas // "Every connection you make emits a signal, so duplicate connections emit two signals" if(m_canvas) m_canvas->disconnectCanvasObserver(this); m_canvas = canvas; } void ImageDockerDock::addCurrentPathToHistory() { m_history.push_back(m_model->filePath(m_proxyModel->mapToSource(m_ui->treeView->rootIndex()))); } void ImageDockerDock::updatePath(const QString& path) { m_ui->bnBack->setDisabled(m_history.empty()); m_imageStripScene->setCurrentDirectory(path); } qint64 ImageDockerDock::generateImageID() const { static qint64 id = 0; return ++id; } void ImageDockerDock::setCurrentImage(qint64 imageID) { if(m_imgInfoMap.contains(m_currImageID)) m_imgInfoMap[m_currImageID].scrollPos = m_ui->imgView->getScrollPos(); m_ui->bnImgClose->setDisabled(imageID < 0); m_ui->bnPopup->setDisabled(imageID < 0); if(imageID < 0) { m_currImageID = -1; m_ui->imgView->setPixmap(QPixmap()); } else if(m_imgInfoMap.contains(imageID)) { ImageInfoIter info = m_imgInfoMap.find(imageID); m_ui->imgView->blockSignals(true); m_ui->imgView->setPixmap(info->pixmap); setZoom(*info); m_ui->imgView->blockSignals(false); m_ui->bnImgPrev->setDisabled(info == m_imgInfoMap.begin()); m_ui->bnImgNext->setDisabled((info+1) == m_imgInfoMap.end()); m_ui->cmbImg->blockSignals(true); m_ui->cmbImg->setCurrentIndex(m_imgListModel->indexFromID(imageID)); m_ui->cmbImg->blockSignals(false); m_currImageID = imageID; } } void ImageDockerDock::setZoom(const ImageInfo& info) { m_ui->imgView->setViewMode(info.viewMode, info.scale); m_ui->imgView->setScrollPos(info.scrollPos); int zoom = qRound(m_ui->imgView->getScale() * 100.0f); m_popupUi->zoomSlider->blockSignals(true); m_popupUi->zoomSlider->setValue(zoom); m_popupUi->zoomSlider->blockSignals(false); } +void ImageDockerDock::saveConfigState() +{ + const QString lastUsedDirectory = m_model->filePath(m_proxyModel->mapToSource(m_ui->treeView->rootIndex())); + + KConfigGroup cfg = KSharedConfig::openConfig()->group("referenceImageDocker"); + cfg.writeEntry("lastUsedDirectory", lastUsedDirectory); +} + +void ImageDockerDock::loadConfigState() +{ + const QString defaultLocation = QDesktopServices::storageLocation(QDesktopServices::PicturesLocation); + + KConfigGroup cfg = KSharedConfig::openConfig()->group("referenceImageDocker"); + QString lastUsedDirectory = cfg.readEntry("lastUsedDirectory", defaultLocation); + + if (!QFileInfo(lastUsedDirectory).exists()) { + lastUsedDirectory = defaultLocation; + } + + m_model->setRootPath(lastUsedDirectory); + m_ui->treeView->setRootIndex(m_proxyModel->mapFromSource(m_model->index(lastUsedDirectory))); + updatePath(lastUsedDirectory); +} + // ------------ slots ------------------------------------------------- // void ImageDockerDock::slotItemDoubleClicked(const QModelIndex& index) { QModelIndex mappedIndex = m_proxyModel->mapToSource(index); mappedIndex = m_model->index(mappedIndex.row(), 0, mappedIndex.parent()); QString path(m_model->filePath(mappedIndex)); if(m_model->isDir(mappedIndex)) { addCurrentPathToHistory(); updatePath(path); m_ui->treeView->setRootIndex(m_proxyModel->mapFromSource(mappedIndex)); } else slotOpenImage(path); } void ImageDockerDock::slotBackButtonClicked() { if(!m_history.empty()) { QString path = m_history.last(); QModelIndex index = m_proxyModel->mapFromSource(m_model->index(path)); m_ui->treeView->setRootIndex(index); m_history.pop_back(); updatePath(path); } } void ImageDockerDock::slotHomeButtonClicked() { addCurrentPathToHistory(); QModelIndex index = m_proxyModel->mapFromSource(m_model->index(QDir::homePath())); m_ui->treeView->setRootIndex(index); updatePath(QDir::homePath()); } void ImageDockerDock::slotUpButtonClicked() { addCurrentPathToHistory(); QModelIndex index = m_proxyModel->mapToSource(m_ui->treeView->rootIndex()); QDir dir(m_model->filePath(index)); dir.makeAbsolute(); if(dir.cdUp()) { index = m_proxyModel->mapFromSource(m_model->index(dir.path())); m_ui->treeView->setRootIndex(index); updatePath(dir.path()); } } void ImageDockerDock::slotOpenImage(const QString& path) { QPixmap pixmap(path); if(!pixmap.isNull()) { QFileInfo fileInfo(path); ImageInfo imgInfo; imgInfo.id = generateImageID(); imgInfo.name = fileInfo.fileName(); imgInfo.path = fileInfo.absoluteFilePath(); imgInfo.viewMode = ImageView::VIEW_MODE_FIT; imgInfo.scale = 1.0f; imgInfo.pixmap = pixmap; imgInfo.scrollPos = QPoint(0, 0); m_imgInfoMap[imgInfo.id] = imgInfo; m_imgListModel->addImage(imgInfo.pixmap, imgInfo.name, imgInfo.id); setCurrentImage(imgInfo.id); m_ui->tabWidget->setCurrentIndex(1); } } void ImageDockerDock::slotCloseCurrentImage() { ImageInfoIter info = m_imgInfoMap.find(m_currImageID); if(info != m_imgInfoMap.end()) { ImageInfoIter next = info + 1; ImageInfoIter prev = info - 1; qint64 id = -1; if(next != m_imgInfoMap.end()) id = next->id; else if(info != m_imgInfoMap.begin()) id = prev->id; m_imgListModel->removeImage(info->id); m_imgInfoMap.erase(info); setCurrentImage(id); if(id < 0) m_ui->tabWidget->setCurrentIndex(0); } } void ImageDockerDock::slotNextImage() { ImageInfoIter info = m_imgInfoMap.find(m_currImageID); if(info != m_imgInfoMap.end()) { ++info; if(info != m_imgInfoMap.end()) setCurrentImage(info->id); } } void ImageDockerDock::slotPrevImage() { ImageInfoIter info = m_imgInfoMap.find(m_currImageID); if(info != m_imgInfoMap.end() && info != m_imgInfoMap.begin()) { --info; setCurrentImage(info->id); } } void ImageDockerDock::slotImageChoosenFromComboBox(int index) { setCurrentImage(m_imgListModel->imageID(index)); } void ImageDockerDock::slotZoomChanged(int zoom) { if(isImageLoaded()) { ImageInfoIter info = m_imgInfoMap.find(m_currImageID); switch(zoom) { case ImageView::VIEW_MODE_FIT: case ImageView::VIEW_MODE_ADJUST: info->viewMode = zoom; break; default: info->viewMode = ImageView::VIEW_MODE_FREE; info->scale = float(zoom) / 100.0f; break; } setZoom(*info); } } void ImageDockerDock::slotColorSelected(const QColor& color) { if (m_canvas) { m_canvas->resourceManager()->setForegroundColor(KoColor(color, KoColorSpaceRegistry::instance()->rgb8())); } } void ImageDockerDock::slotViewModeChanged(int viewMode, qreal scale) { if(isImageLoaded()) { m_imgInfoMap[m_currImageID].viewMode = viewMode; m_imgInfoMap[m_currImageID].scale = scale; int zoom = qRound(scale * 100.0); m_popupUi->zoomSlider->blockSignals(true); m_popupUi->zoomSlider->setValue(zoom); m_popupUi->zoomSlider->blockSignals(false); } } void ImageDockerDock::slotCloseZoomPopup() { m_ui->bnPopup->hidePopupWidget(); } void ImageDockerDock::slotChangeRoot(const QString &path) { m_model->setRootPath(path); m_ui->treeView->setRootIndex(m_proxyModel->mapFromSource(m_model->index(path))); updatePath(path); } bool ImageDockerDock::eventFilter(QObject *obj, QEvent *event) { Q_UNUSED(obj); if (event->type() == QEvent::Resize) { m_ui->treeView->setColumnWidth(0, width()); return true; } return false; } diff --git a/plugins/dockers/imagedocker/imagedocker_dock.h b/plugins/dockers/imagedocker/imagedocker_dock.h index c5df038db2..c02f7cfd09 100644 --- a/plugins/dockers/imagedocker/imagedocker_dock.h +++ b/plugins/dockers/imagedocker/imagedocker_dock.h @@ -1,111 +1,115 @@ /* * 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; 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 H_IMAGEDOCKER_DOCK_H_ #define H_IMAGEDOCKER_DOCK_H_ #include #include #include #include #include #include #include class QModelIndex; class QFileSystemModel; class QButtonGroup; class ImageFilter; class ImageStripScene; class ImageListModel; class QTemporaryFile; struct ImageDockerUI; struct PopupWidgetUI; class ImageDockerDock: public QDockWidget, public KoCanvasObserverBase { Q_OBJECT struct ImageInfo { qint64 id; int viewMode; QString path; QString name; float scale; QPixmap pixmap; QPoint scrollPos; }; typedef QMap::iterator ImageInfoIter; public: ImageDockerDock(); ~ImageDockerDock() override; QString observerName() override { return "ImageDockerDock"; } void setCanvas(KoCanvasBase* canvas) override; void unsetCanvas() override { m_canvas = 0; // Intentionally not disabled if there's no canvas } private Q_SLOTS: void slotItemDoubleClicked(const QModelIndex& index); void slotBackButtonClicked(); void slotUpButtonClicked(); void slotHomeButtonClicked(); void slotCloseCurrentImage(); void slotNextImage(); void slotPrevImage(); void slotOpenImage(const QString& path); void slotImageChoosenFromComboBox(int index); void slotZoomChanged(int zoom); void slotColorSelected(const QColor& color); void slotViewModeChanged(int viewMode, qreal scale); void slotCloseZoomPopup(); void slotChangeRoot(const QString& path); protected: bool eventFilter(QObject *obj, QEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; void showEvent(QShowEvent *) override; private: void addCurrentPathToHistory(); void updatePath(const QString& path); qint64 generateImageID() const; void setCurrentImage(qint64 imageID); bool isImageLoaded() const { return m_currImageID != -1; } void setZoom(const ImageInfo& info); + void saveConfigState(); + void loadConfigState(); + + private: QFileSystemModel* m_model; QButtonGroup* m_zoomButtons; QPointer m_canvas; ImageFilter* m_proxyModel; ImageListModel* m_imgListModel; QStringList m_history; ImageStripScene* m_imageStripScene; ImageDockerUI* m_ui; PopupWidgetUI* m_popupUi; QMap m_imgInfoMap; qint64 m_currImageID; QList m_temporaryFiles; }; #endif // H_IMAGEDOCKER_DOCK_H_ diff --git a/plugins/extensions/animationrenderer/AnimationRenderer.cpp b/plugins/extensions/animationrenderer/AnimationRenderer.cpp index d65734ec56..790080b870 100644 --- a/plugins/extensions/animationrenderer/AnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/AnimationRenderer.cpp @@ -1,199 +1,198 @@ /* * Copyright (c) 2016 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 "AnimationRenderer.h" #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include "DlgAnimationRenderer.h" K_PLUGIN_FACTORY_WITH_JSON(AnimaterionRendererFactory, "kritaanimationrenderer.json", registerPlugin();) AnimaterionRenderer::AnimaterionRenderer(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { // Shows the big dialog KisAction *action = createAction("render_animation"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderAnimation())); // Re-renders the image sequence as defined in the last render action = createAction("render_image_sequence_again"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderSequenceAgain())); } AnimaterionRenderer::~AnimaterionRenderer() { } void AnimaterionRenderer::slotRenderAnimation() { KisImageWSP image = m_view->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = m_view->document(); - doc->setFileProgressProxy(); - doc->setFileProgressUpdater(i18n("Export frames")); + KoUpdaterPtr updater = m_view->createThreadedUpdater(i18n("Export frames")); DlgAnimationRenderer dlgAnimationRenderer(doc, m_view->mainWindow()); dlgAnimationRenderer.setCaption(i18n("Render Animation")); KisConfig kisConfig; KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->fromXML(kisConfig.exportConfiguration("IMAGESEQUENCE")); dlgAnimationRenderer.setSequenceConfiguration(cfg); cfg->clearProperties(); cfg->fromXML(kisConfig.exportConfiguration("ANIMATION_RENDERER")); dlgAnimationRenderer.setVideoConfiguration(cfg); cfg->clearProperties(); cfg->fromXML(kisConfig.exportConfiguration("FFMPEG_CONFIG")); dlgAnimationRenderer.setEncoderConfiguration(cfg); // update the UI to show the selected export options dlgAnimationRenderer.updateExportUIOptions(); if (dlgAnimationRenderer.exec() == QDialog::Accepted) { KisPropertiesConfigurationSP sequenceConfig = dlgAnimationRenderer.getSequenceConfiguration(); kisConfig.setExportConfiguration("IMAGESEQUENCE", sequenceConfig); QString mimetype = sequenceConfig->getString("mimetype"); QString extension = KisMimeDatabase::suffixesForMimeType(mimetype).first(); QString baseFileName = QString("%1/%2.%3").arg(sequenceConfig->getString("directory")) .arg(sequenceConfig->getString("basename")) .arg(extension); - KisAnimationExportSaver exporter(doc, baseFileName, sequenceConfig->getInt("first_frame"), sequenceConfig->getInt("last_frame"), sequenceConfig->getInt("sequence_start")); - KisImportExportFilter::ConversionStatus status = - exporter.exportAnimation(dlgAnimationRenderer.getFrameExportConfiguration()); + KisAnimationExportSaver exporter(doc, baseFileName, + sequenceConfig->getInt("first_frame"), + sequenceConfig->getInt("last_frame"), + sequenceConfig->getInt("sequence_start"), + updater); - if (status != KisImportExportFilter::OK) { - const QString msg = KisImportExportFilter::conversionStatusString(status); - QMessageBox::critical(0, i18nc("@title:window", "Krita"), - i18n("Could not export animation frames:\n%1", msg)); - } else { + bool success = exporter.exportAnimation(dlgAnimationRenderer.getFrameExportConfiguration()) == KisImportExportFilter::OK; + + // the folder could have been read-only or something else could happen + if (success) { QString savedFilesMask = exporter.savedFilesMask(); KisPropertiesConfigurationSP videoConfig = dlgAnimationRenderer.getVideoConfiguration(); if (videoConfig) { kisConfig.setExportConfiguration("ANIMATION_RENDERER", videoConfig); KisPropertiesConfigurationSP encoderConfig = dlgAnimationRenderer.getEncoderConfiguration(); if (encoderConfig) { kisConfig.setExportConfiguration("FFMPEG_CONFIG", encoderConfig); encoderConfig->setProperty("savedFilesMask", savedFilesMask); } const QString fileName = videoConfig->getString("filename"); QString resultFile = fileName; KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(resultFile).isAbsolute()) { const QFileInfo info(resultFile); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); } QSharedPointer encoder = dlgAnimationRenderer.encoderFilter(); encoder->setMimeType(mimetype.toLatin1()); QFile fi(resultFile); KisImportExportFilter::ConversionStatus res; if (!fi.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << fi.fileName() << "for writing!"; res = KisImportExportFilter::CreationError; } else { encoder->setFilename(fi.fileName()); res = encoder->convert(doc, &fi, encoderConfig); fi.close(); } if (res != KisImportExportFilter::OK) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage())); } if (videoConfig->getBool("delete_sequence", false)) { QDir d(sequenceConfig->getString("directory")); QStringList sequenceFiles = d.entryList(QStringList() << sequenceConfig->getString("basename") + "*." + extension, QDir::Files); Q_FOREACH(const QString &f, sequenceFiles) { d.remove(f); } } } } } - - doc->clearFileProgressUpdater(); - doc->clearFileProgressProxy(); - } void AnimaterionRenderer::slotRenderSequenceAgain() { KisImageWSP image = m_view->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = m_view->document(); - doc->setFileProgressProxy(); doc->setFileProgressUpdater(i18n("Export frames")); + KoUpdaterPtr updater = m_view->createThreadedUpdater(i18n("Export frames")); KisConfig kisConfig; KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->fromXML(kisConfig.exportConfiguration("IMAGESEQUENCE")); QString mimetype = cfg->getString("mimetype"); QString extension = KisMimeDatabase::suffixesForMimeType(mimetype).first(); QString baseFileName = QString("%1/%2.%3").arg(cfg->getString("directory")) .arg(cfg->getString("basename")) .arg(extension); - KisAnimationExportSaver exporter(doc, baseFileName, cfg->getInt("first_frame"), cfg->getInt("last_frame"), cfg->getInt("sequence_start")); - bool success = exporter.exportAnimation(); - Q_ASSERT(success); - doc->clearFileProgressUpdater(); - doc->clearFileProgressProxy(); + KisAnimationExportSaver exporter(doc, baseFileName, + cfg->getInt("first_frame"), + cfg->getInt("last_frame"), + cfg->getInt("sequence_start"), + updater); + bool success = exporter.exportAnimation(); + Q_ASSERT(success); } #include "AnimationRenderer.moc" diff --git a/plugins/extensions/bigbrother/bigbrother.cc b/plugins/extensions/bigbrother/bigbrother.cc index 824b5bdc00..0586196030 100644 --- a/plugins/extensions/bigbrother/bigbrother.cc +++ b/plugins/extensions/bigbrother/bigbrother.cc @@ -1,238 +1,238 @@ /* * Copyright (c) 2007 Cyrille Berger (cberger@cberger.net) * * 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 "bigbrother.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 "actionseditor/kis_actions_editor.h" #include "actionseditor/kis_actions_editor_dialog.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(BigBrotherPluginFactory, "kritabigbrother.json", registerPlugin();) class RecordedActionSaveContext : public KisRecordedActionSaveContext { public: void saveGradient(const KoAbstractGradient* ) override {} void savePattern(const KoPattern* ) override {} }; class RecordedActionLoadContext : public KisRecordedActionLoadContext { public: KoAbstractGradient* gradient(const QString& name) const override { return KoResourceServerProvider::instance()->gradientServer()->resourceByName(name); } KoPattern* pattern(const QString& name) const override { return KoResourceServerProvider::instance()->patternServer()->resourceByName(name); } }; BigBrotherPlugin::BigBrotherPlugin(QObject *parent, const QVariantList &) : KisViewPlugin(parent) , m_recorder(0) { if (parent->inherits("KisViewManager")) { m_view = (KisViewManager*) parent; // Open and play action KisAction* action = createAction("Macro_Open_Play"); connect(action, SIGNAL(triggered()), this, SLOT(slotOpenPlay())); // Open and edit action action = createAction("Macro_Open_Edit"); connect(action, SIGNAL(triggered()), this, SLOT(slotOpenEdit())); // Start recording action m_startRecordingMacroAction = createAction("Recording_Start_Recording_Macro"); connect(m_startRecordingMacroAction, SIGNAL(triggered()), this, SLOT(slotStartRecordingMacro())); // Save recorded action m_stopRecordingMacroAction = createAction("Recording_Stop_Recording_Macro"); connect(m_stopRecordingMacroAction, SIGNAL(triggered()), this, SLOT(slotStopRecordingMacro())); m_stopRecordingMacroAction->setEnabled(false); } } BigBrotherPlugin::~BigBrotherPlugin() { m_view = 0; delete m_recorder; } void BigBrotherPlugin::slotOpenPlay() { KisMacro* m = openMacro(); dbgKrita << m; if (!m) return; dbgPlugins << "Play the macro"; - KoProgressUpdater* updater = m_view->createProgressUpdater(); - updater->start(1, i18n("Playing back macro")); - KisMacroPlayer player(m, KisPlayInfo(m_view->image(), m_view->activeNode()), updater->startSubtask()); + KoUpdaterPtr updater = m_view->createUnthreadedUpdater(i18n("Playing back macro")); + KisMacroPlayer player(m, KisPlayInfo(m_view->image(), m_view->activeNode()), updater); player.start(); while(player.isRunning()) { QApplication::processEvents(); } dbgPlugins << "Finished"; delete m; } void BigBrotherPlugin::slotOpenEdit() { KisMacro *macro = openMacro(); if (!macro) return; KisActionsEditorDialog aed(m_view->mainWindow()); aed.actionsEditor()->setMacro(macro); if (aed.exec() == QDialog::Accepted) { saveMacro(macro); } delete macro; } void BigBrotherPlugin::slotStartRecordingMacro() { dbgPlugins << "Start recording macro"; if (m_recorder) return; // Alternate actions m_startRecordingMacroAction->setEnabled(false); m_stopRecordingMacroAction->setEnabled(true); // Create recorder m_recorder = new KisMacro(); connect(m_view->image()->actionRecorder(), SIGNAL(addedAction(const KisRecordedAction&)), m_recorder, SLOT(addAction(const KisRecordedAction&))); } void BigBrotherPlugin::slotStopRecordingMacro() { dbgPlugins << "Stop recording macro"; if (!m_recorder) return; // Alternate actions m_startRecordingMacroAction->setEnabled(true); m_stopRecordingMacroAction->setEnabled(false); // Save the macro saveMacro(m_recorder); // Delete recorder delete m_recorder; m_recorder = 0; } KisMacro* BigBrotherPlugin::openMacro() { QStringList mimeFilter; mimeFilter << "*.krarec|Recorded actions (*.krarec)"; KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::OpenFile, "OpenDocument"); dialog.setCaption(i18n("Open Macro")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/krita-recorded-macro", "application/krita-recorded-macro"); QString filename = dialog.filename(); RecordedActionLoadContext loadContext; if (!filename.isNull()) { QDomDocument doc; QFile f(filename); if (f.exists()) { dbgPlugins << f.open(QIODevice::ReadOnly); QString err; int line, col; if (!doc.setContent(&f, &err, &line, &col)) { // TODO error message dbgPlugins << err << " line = " << line << " col = " << col; f.close(); return 0; } f.close(); QDomElement docElem = doc.documentElement(); if (!docElem.isNull() && docElem.tagName() == "RecordedActions") { dbgPlugins << "Load the macro"; KisMacro* m = new KisMacro(); m->fromXML(docElem, &loadContext); return m; } else { // TODO error message } } else { dbgPlugins << "Unexistant file : " << filename; } } return 0; } void BigBrotherPlugin::saveMacro(const KisMacro* macro) { KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::SaveFile, "bigbrother"); dialog.setCaption(i18n("Save Macro")); dialog.setMimeTypeFilters(QStringList() << "application/krita-recorded-macro", "application/krita-recorded-macro"); QString filename = dialog.filename(); if (!filename.isNull()) { QDomDocument doc; QDomElement e = doc.createElement("RecordedActions"); RecordedActionSaveContext context; macro->toXML(doc, e, &context); doc.appendChild(e); QFile f(filename); f.open(QIODevice::WriteOnly); QTextStream stream(&f); stream.setCodec("UTF-8"); doc.save(stream, 2); f.close(); } } #include "bigbrother.moc" diff --git a/plugins/extensions/imagesplit/imagesplit.cpp b/plugins/extensions/imagesplit/imagesplit.cpp index 85df7d958c..884d0b34a6 100644 --- a/plugins/extensions/imagesplit/imagesplit.cpp +++ b/plugins/extensions/imagesplit/imagesplit.cpp @@ -1,208 +1,207 @@ /* * imagesplit.cc -- Part of Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * 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 "imagesplit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_imagesplit.h" K_PLUGIN_FACTORY_WITH_JSON(ImagesplitFactory, "kritaimagesplit.json", registerPlugin();) Imagesplit::Imagesplit(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("imagesplit"); connect(action, SIGNAL(triggered()), this, SLOT(slotImagesplit())); } Imagesplit::~Imagesplit() { } bool Imagesplit::saveAsImage(const QRect &imgSize, const QString &mimeType, const QString &url) { KisImageSP image = m_view->image(); KisDocument *document = KisPart::instance()->createDocument(); KisImageSP dst = new KisImage(document->createUndoStore(), imgSize.width(), imgSize.height(), image->colorSpace(), image->objectName()); dst->setResolution(image->xRes(), image->yRes()); document->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, dst->nextLayerName(), 255); KisPainter gc(paintLayer->paintDevice()); gc.bitBlt(QPoint(0, 0), image->projection(), imgSize); dst->addNode(paintLayer, KisNodeSP(0)); dst->refreshGraph(); - document->setOutputMimeType(mimeType.toLatin1()); document->setFileBatchMode(true); - if (!document->exportDocument(QUrl::fromLocalFile(url))) { + if (!document->exportDocumentSync(QUrl::fromLocalFile(url), mimeType.toLatin1())) { if (document->errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", document->localFilePath())); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", document->localFilePath(), document->errorMessage())); } return false; } delete document; return true; } void Imagesplit::slotImagesplit() { // Taking the title - url from caption function and removing file extension QStringList strList = ((m_view->document())->caption()).split('.'); QString suffix = strList.at(0); // Getting all mime types and converting them into names which are displayed at combo box QStringList listMimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); QString defaultMime = QString::fromLatin1(m_view->document()->mimeType()); int defaultMimeIndex = 0; listMimeFilter.sort(); QStringList filteredMimeTypes; QStringList listFileType; int i = 0; Q_FOREACH (const QString & mimeType, listMimeFilter) { listFileType.append(KisMimeDatabase::descriptionForMimeType(mimeType)); filteredMimeTypes.append(mimeType); if (mimeType == defaultMime) { defaultMimeIndex = i; } i++; } listMimeFilter = filteredMimeTypes; Q_ASSERT(listMimeFilter.size() == listFileType.size()); DlgImagesplit *dlgImagesplit = new DlgImagesplit(m_view, suffix, listFileType, defaultMimeIndex); dlgImagesplit->setObjectName("Imagesplit"); Q_CHECK_PTR(dlgImagesplit); KisImageWSP image = m_view->image(); if (dlgImagesplit->exec() == QDialog::Accepted) { int numHorizontalLines = dlgImagesplit->horizontalLines(); int numVerticalLines = dlgImagesplit->verticalLines(); int img_width = image->width() / (numVerticalLines + 1); int img_height = image->height() / (numHorizontalLines + 1); bool stop = false; if (dlgImagesplit->autoSave()) { KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::OpenDirectory, "OpenDocument"); dialog.setCaption(i18n("Save Image on Split")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); QStringList mimeFilter = m_view->document()->importExportManager()->mimeFilter(KisImportExportManager::Export); QString defaultMime = QString::fromLatin1(m_view->document()->mimeType()); dialog.setMimeTypeFilters(mimeFilter, defaultMime); QUrl directory = QUrl::fromUserInput(dialog.filename()); if (directory.isEmpty()) return; for (int i = 0, k = 1; i < (numVerticalLines + 1); i++) { for (int j = 0; j < (numHorizontalLines + 1); j++, k++) { QString mimeTypeSelected = listMimeFilter.at(dlgImagesplit->cmbIndex); QString homepath = directory.toLocalFile(); QString suffix = KisMimeDatabase::suffixesForMimeType(mimeTypeSelected).first(); qDebug() << "suffix" << suffix; if (suffix.startsWith("*.")) { suffix = suffix.remove(0, 1); } qDebug() << "\tsuffix" << suffix; if (!suffix.startsWith(".")) { suffix = suffix.prepend('.'); } qDebug() << "\tsuffix" << suffix; QString fileName = dlgImagesplit->suffix() + '_' + QString::number(k) + suffix; QString url = homepath + '/' + fileName; if (!saveAsImage(QRect((i * img_width), (j * img_height), img_width, img_height), listMimeFilter.at(dlgImagesplit->cmbIndex), url)) { stop = true; break; } } if (stop) { break; } } } else { for (int i = 0; i < (numVerticalLines + 1); i++) { for (int j = 0; j < (numHorizontalLines + 1); j++) { KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Save Image on Split")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(listMimeFilter, defaultMime); QUrl url = QUrl::fromUserInput(dialog.filename()); QString mimefilter = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); if (url.isEmpty()) return; if (!saveAsImage(QRect((i * img_width), (j * img_height), img_width, img_height), mimefilter, url.toLocalFile())) { stop = true; break; } } if (stop) { break; } } } } delete dlgImagesplit; } #include "imagesplit.moc" diff --git a/plugins/extensions/layersplit/layersplit.cpp b/plugins/extensions/layersplit/layersplit.cpp index a288da7199..522980f5ca 100644 --- a/plugins/extensions/layersplit/layersplit.cpp +++ b/plugins/extensions/layersplit/layersplit.cpp @@ -1,228 +1,226 @@ /* * 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 "layersplit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_layersplit.h" #include "kis_node_manager.h" #include "kis_node_commands_adapter.h" #include "kis_undo_adapter.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(LayerSplitFactory, "kritalayersplit.json", registerPlugin();) LayerSplit::LayerSplit(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("layersplit"); connect(action, SIGNAL(triggered()), this, SLOT(slotLayerSplit())); } LayerSplit::~LayerSplit() { } struct Layer { KoColor color; KisPaintDeviceSP device; KisRandomAccessorSP accessor; int pixelsWritten; bool operator<(const Layer& other) const { return pixelsWritten < other.pixelsWritten; } }; void LayerSplit::slotLayerSplit() { DlgLayerSplit dlg; if (dlg.exec() == QDialog::Accepted) { dlg.hide(); QApplication::setOverrideCursor(Qt::WaitCursor); - KoProgressUpdater* pu = m_view->createProgressUpdater(KoProgressUpdater::Unthreaded); - pu->start(100, i18n("Split into Layers")); - QPointer updater = pu->startSubtask(); + QPointer updater = m_view->createUnthreadedUpdater(i18n("Split into Layers")); KisImageSP image = m_view->image(); if (!image) return; image->lock(); KisNodeSP node = m_view->activeNode(); if (!node) return; KisPaintDeviceSP projection = node->projection(); if (!projection) return; QList colorMap; const KoColorSpace *cs = projection->colorSpace(); QRect rc = image->bounds(); int fuzziness = dlg.fuzziness(); updater->setProgress(0); KisRandomConstAccessorSP acc = projection->createRandomConstAccessorNG(rc.x(), rc.y()); for (int row = rc.y(); row < rc.height(); ++row) { for (int col = rc.x(); col < rc.width(); ++col) { acc->moveTo(col, row); KoColor c(cs); c.setColor(acc->rawDataConst(), cs); if (c.opacityU8() == OPACITY_TRANSPARENT_U8) { continue; } if (dlg.disregardOpacity()) { c.setOpacity(OPACITY_OPAQUE_U8); } bool found = false; Q_FOREACH (const Layer &l, colorMap) { if (fuzziness == 0) { found = (l.color == c); } else { quint8 match = cs->difference(l.color.data(), c.data()); found = (match <= fuzziness); } if (found) { KisRandomAccessorSP dstAcc = l.accessor; dstAcc->moveTo(col, row); memcpy(dstAcc->rawData(), acc->rawDataConst(), cs->pixelSize()); const_cast(&l)->pixelsWritten++; break; } } if (!found) { QString name = ""; if (dlg.palette()) { name = dlg.palette()->closestColorName(c); } if (name.toLower() == "untitled" || name.toLower() == "none" || name.toLower() == "") { name = KoColor::toQString(c); } Layer l; l.color = c; l.device = new KisPaintDevice(cs, name); l.accessor = l.device->createRandomAccessorNG(col, row); l.accessor->moveTo(col, row); memcpy(l.accessor->rawData(), acc->rawDataConst(), cs->pixelSize()); l.pixelsWritten = 1; colorMap << l; } } if (updater->interrupted()) { return; } updater->setProgress((row - rc.y()) * 100 / rc.height() - rc.y()); } updater->setProgress(100); dbgKrita << "Created" << colorMap.size() << "layers"; // Q_FOREACH (const Layer &l, colorMap) { // dbgKrita << "\t" << l.device->objectName() << ":" << l.pixelsWritten; // } if (dlg.sortLayers()) { std::sort(colorMap.begin(), colorMap.end()); } KisUndoAdapter *undo = image->undoAdapter(); undo->beginMacro(kundo2_i18n("Split Layer")); KisNodeCommandsAdapter adapter(m_view); KisGroupLayerSP baseGroup = dynamic_cast(node->parent().data()); if (!baseGroup) { // Masks are never nested baseGroup = dynamic_cast(node->parent()->parent().data()); } if (dlg.hideOriginal()) { node->setVisible(false); } if (dlg.createBaseGroup()) { KisGroupLayerSP grp = new KisGroupLayer(image, i18n("Color"), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); baseGroup = grp; } Q_FOREACH (const Layer &l, colorMap) { KisGroupLayerSP grp = baseGroup; if (dlg.createSeparateGroups()) { grp = new KisGroupLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); } KisPaintLayerSP paintLayer = new KisPaintLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8, l.device); adapter.addNode(paintLayer, grp, 0); paintLayer->setAlphaLocked(dlg.lockAlpha()); } undo->endMacro(); image->unlock(); image->setModified(); } QApplication::restoreOverrideCursor(); } #include "layersplit.moc" diff --git a/plugins/extensions/pykrita/CMakeLists.txt b/plugins/extensions/pykrita/CMakeLists.txt index 4994adb102..1e7db0c56d 100644 --- a/plugins/extensions/pykrita/CMakeLists.txt +++ b/plugins/extensions/pykrita/CMakeLists.txt @@ -1,34 +1,36 @@ -find_package(PythonLibrary) +# PythonLibrary should've been found in the root CMakeLists.txt set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "http://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) - + find_package(SIP "4.18.0") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") -macro_bool_to_01(SIP_FOUND HAVE_SIP) +macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") -macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) +macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) if (HAVE_PYQT5 AND HAVE_SIP AND HAVE_PYTHONLIBS) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SIP_INCLUDE_DIR} ${PYTHON_INCLUDE_PATH}) - + add_subdirectory(sip) add_subdirectory(plugin) add_subdirectory(kritarunner) +add_test(pythonUnitTests ${PYTHON_EXECUTABLE} -m unittest discover "${CMAKE_INSTALL_PREFIX}" "${CMAKE_SOURCE_DIR}" -s ${CMAKE_SOURCE_DIR}/plugins/extensions/pykrita/tests -p "*_test.py") + endif () diff --git a/plugins/extensions/pykrita/kritarunner/CMakeLists.txt b/plugins/extensions/pykrita/kritarunner/CMakeLists.txt index 2e3443d3ff..eec2049b58 100644 --- a/plugins/extensions/pykrita/kritarunner/CMakeLists.txt +++ b/plugins/extensions/pykrita/kritarunner/CMakeLists.txt @@ -1,30 +1,33 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../plugin ${CMAKE_CURRENT_BINARY_DIR}/../plugin ${CMAKE_CURRENT_SOURCE_DIR}/../libkis ${CMAKE_CURRENT_BINARY_DIR}/../libkis ) set(kritarunner_SRCS main.cpp ../plugin/engine.cpp ../plugin/plugin.cpp ../plugin/pyqtpluginsettings.cpp ../plugin/utilities.cpp ) add_executable(kritarunner ${kritarunner_SRCS}) target_link_libraries(kritarunner PRIVATE ${PYTHON_LIBRARY} kritaui kritalibkis Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Xml Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Concurrent) -install(TARGETS kritarunner ${INSTALL_TARGETS_DEFAULT_ARGS}) +if (MINGW) + target_compile_definitions(kritarunner PRIVATE _hypot=hypot) +endif (MINGW) +install(TARGETS kritarunner ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/plugins/extensions/pykrita/plugin/CMakeLists.txt b/plugins/extensions/pykrita/plugin/CMakeLists.txt index 7ca32184ac..52ac98a2e7 100644 --- a/plugins/extensions/pykrita/plugin/CMakeLists.txt +++ b/plugins/extensions/pykrita/plugin/CMakeLists.txt @@ -1,38 +1,42 @@ # NOTE Disable trivial Qt keywords due conflicts w/ some Python.h header # (at least version 3.3 of it has a member PyType_Spec::slots) add_definitions(-DQT_NO_KEYWORDS) configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) set(SOURCES plugin.cpp pyqtpluginsettings.cpp utilities.cpp engine.cpp ) ki18n_wrap_ui(SOURCES info.ui manager.ui ) add_library(kritapykrita MODULE ${SOURCES}) target_link_libraries( kritapykrita ${PYTHON_LIBRARY} kritaui kritalibkis ) +if (MINGW) + target_compile_definitions(kritapykrita PRIVATE _hypot=hypot) +endif (MINGW) + install(TARGETS kritapykrita DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) # Install "built-in" api install( DIRECTORY krita DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" ) add_subdirectory(plugins) #add_subdirectory(test) diff --git a/plugins/extensions/pykrita/plugin/engine.cpp b/plugins/extensions/pykrita/plugin/engine.cpp index b66fbeb244..68b0bc099d 100644 --- a/plugins/extensions/pykrita/plugin/engine.cpp +++ b/plugins/extensions/pykrita/plugin/engine.cpp @@ -1,797 +1,799 @@ // This file is part of PyKrita, Krita' Python scripting plugin. // // Copyright (C) 2006 Paul Giannaros // Copyright (C) 2012, 2013 Shaheed Haque // Copyright (C) 2013 Alex Turbov // // 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) version 3, or any // later version accepted by the membership of KDE e.V. (or its // successor approved by the membership of KDE e.V.), which shall // act as a proxy defined in Section 6 of version 3 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 library. If not, see . // #include "engine.h" // config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so // on the build system #include "config.h" #include "utilities.h" #include #include #include #include #include #include #include #include //#include #include #include /// Name of the file where per-plugin configuration is stored. #define CONFIG_FILE "kritapykritarc" #if PY_MAJOR_VERSION < 3 # define PYKRITA_INIT initpykrita #else # define PYKRITA_INIT PyInit_pykrita #endif PyMODINIT_FUNC PYKRITA_INIT(); // fwd decl /// \note Namespace name written in uppercase intentionally! /// It will appear in debug output from Python plugins... namespace PYKRITA { PyObject* debug(PyObject* /*self*/, PyObject* args) { const char* text; if (PyArg_ParseTuple(args, "s", &text)) dbgScript << text; Py_INCREF(Py_None); return Py_None; } } // namespace PYKRITA namespace { PyObject* s_pykrita; /** * \attention Krita has embedded Python, so init function \b never will be called * automatically! We can use this fact to initialize a pointer to an instance * of the \c Engine class (which is a part of the \c Plugin), so exported * functions will know it (yep, from Python's side they should be static). */ PyKrita::Engine* s_engine_instance = 0; /** * Wrapper function, called explicitly from \c Engine::Engine * to initialize pointer to the only (by design) instance of the engine, * so exported (to Python) functions get know it... Then invoke * a real initialization sequence... */ void pythonInitwrapper(PyKrita::Engine* const engine) { Q_ASSERT("Sanity check" && !s_engine_instance); s_engine_instance = engine; // Call initialize explicitly to initialize embedded interpreter. PYKRITA_INIT(); } /** * Functions for the Python module called pykrita. * \todo Does it \b REALLY needed? Configuration data will be flushed * on exit anyway! Why to write it (and even allow to plugins to call this) * \b before krita really going to exit? It would be better to \b deprecate * this (harmful) function! */ PyObject* pykritaSaveConfiguration(PyObject* /*self*/, PyObject* /*unused*/) { if (s_engine_instance) s_engine_instance->saveGlobalPluginsConfiguration(); Py_INCREF(Py_None); return Py_None; } PyMethodDef pykritaMethods[] = { { "saveConfiguration" , &pykritaSaveConfiguration , METH_NOARGS , "Save the configuration of the plugin into " CONFIG_FILE } , { "qDebug" , &PYKRITA::debug , METH_VARARGS , "True KDE way to show debug info" } , { 0, 0, 0, 0 } }; } // anonymous namespace //BEGIN Python module registration PyMODINIT_FUNC PYKRITA_INIT() { #if PY_MAJOR_VERSION < 3 s_pykrita = Py_InitModule3("pykrita", pykritaMethods, "The pykrita module"); PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__); #else static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT , "pykrita" , "The pykrita module" , -1 , pykritaMethods , 0 , 0 , 0 , 0 }; s_pykrita = PyModule_Create(&moduledef); PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__); return s_pykrita; #endif } //END Python module registration //BEGIN PyKrita::Engine::PluginState PyKrita::Engine::PluginState::PluginState() : m_enabled(false) , m_broken(false) , m_unstable(false) , m_isDir(false) { } //END PyKrita::Engine::PluginState /** * Just initialize some members. The second (most important) part * is to call \c Engine::tryInitializeGetFailureReason()! * W/o that call instance is invalid and using it lead to UB! */ PyKrita::Engine::Engine() : m_configuration(0) , m_sessionConfiguration(0) , m_engineIsUsable(false) { } /// \todo More accurate shutdown required: /// need to keep track what exactly was broken on /// initialize attempt... PyKrita::Engine::~Engine() { dbgScript << "Going to destroy the Python engine"; // Notify Python that engine going to die { Python py = Python(); py.functionCall("_pykritaUnloading"); } unloadAllModules(); // Clean internal configuration dicts // NOTE Do not need to save anything! It's already done! if (m_configuration) { Py_DECREF(m_configuration); } if (m_sessionConfiguration) { Py_DECREF(m_sessionConfiguration); } + Python::maybeFinalize(); Python::libraryUnload(); s_engine_instance = 0; } void PyKrita::Engine::unloadAllModules() { // Unload all modules for (int i = 0; i < m_plugins.size(); ++i) { if (m_plugins[i].isEnabled() && !m_plugins[i].isBroken()) { unloadModule(i); } } } /** * \todo Make sure noone tries to use uninitialized engine! * (Or enable exceptions for this module, so this case wouldn't even araise?) */ QString PyKrita::Engine::tryInitializeGetFailureReason() { dbgScript << "Construct the Python engine for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION; - if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PYKRITA_INIT)) { - return i18nc("@info:tooltip ", "Cannot load built-in pykrita module"); - } - Python::libraryLoad(); - Python py = Python(); + if (!Python::libraryLoad()) { + return i18nc("@info:tooltip ", "Cannot load Python library"); + } // Update PYTHONPATH // 0) custom plugin directories (prefer local dir over systems') // 1) shipped krita module's dir - // 2) w/ site_packages/ dir of the Python QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts"); - pluginDirectories << KoResourcePaths::locate("appdata", "plugins/pykrita/") - << QLatin1String(PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR) - ; dbgScript << "Plugin Directories: " << pluginDirectories; - if (!py.prependPythonPaths(pluginDirectories)) { - return i18nc("@info:tooltip ", "Cannot update Python paths"); + if (!Python::setPath(pluginDirectories)) { + return i18nc("@info:tooltip ", "Cannot set Python paths"); } + if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PYKRITA_INIT)) { + return i18nc("@info:tooltip ", "Cannot load built-in pykrita module"); + } + + Python::ensureInitialized(); + Python py = Python(); + PyRun_SimpleString( "import sip\n" "sip.setapi('QDate', 2)\n" "sip.setapi('QTime', 2)\n" "sip.setapi('QDateTime', 2)\n" "sip.setapi('QUrl', 2)\n" "sip.setapi('QTextStream', 2)\n" "sip.setapi('QString', 2)\n" "sip.setapi('QVariant', 2)\n" ); // Initialize our built-in module. pythonInitwrapper(this); if (!s_pykrita) { return i18nc("@info:tooltip ", "No pykrita built-in module"); } // Setup global configuration m_configuration = PyDict_New(); /// \todo Check \c m_configuration ? // Host the configuration dictionary. py.itemStringSet("configuration", m_configuration); // Setup per session configuration m_sessionConfiguration = PyDict_New(); py.itemStringSet("sessionConfiguration", m_sessionConfiguration); // Initialize 'plugins' dict of module 'pykrita' PyObject* plugins = PyDict_New(); py.itemStringSet("plugins", plugins); // Get plugins available scanPlugins(); // NOTE Empty failure reson string indicates success! m_engineIsUsable = true; return QString(); } int PyKrita::Engine::columnCount(const QModelIndex&) const { return Column::LAST__; } int PyKrita::Engine::rowCount(const QModelIndex&) const { return m_plugins.size(); } QModelIndex PyKrita::Engine::index(const int row, const int column, const QModelIndex& parent) const { if (!parent.isValid() && row < m_plugins.size() && column < Column::LAST__) return createIndex(row, column); return QModelIndex(); } QModelIndex PyKrita::Engine::parent(const QModelIndex&) const { return QModelIndex(); } QVariant PyKrita::Engine::headerData( const int section , const Qt::Orientation orientation , const int role ) const { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { switch (section) { case Column::NAME: return i18nc("@title:column", "Name"); case Column::COMMENT: return i18nc("@title:column", "Comment"); default: break; } } return QVariant(); } QVariant PyKrita::Engine::data(const QModelIndex& index, const int role) const { Q_ASSERT("Sanity check" && index.row() < m_plugins.size()); Q_ASSERT("Sanity check" && index.column() < Column::LAST__); switch (role) { case Qt::DisplayRole: switch (index.column()) { case Column::NAME: return m_plugins[index.row()].m_pythonPlugin.name(); case Column::COMMENT: return m_plugins[index.row()].m_pythonPlugin.comment(); default: break; } break; case Qt::CheckStateRole: { if (index.column() == Column::NAME) { const bool checked = m_plugins[index.row()].isEnabled(); return checked ? Qt::Checked : Qt::Unchecked; } break; } case Qt::ToolTipRole: if (!m_plugins[index.row()].m_errorReason.isEmpty()) return m_plugins[index.row()].m_errorReason; break; case Qt::ForegroundRole: if (m_plugins[index.row()].isUnstable()) { KColorScheme scheme(QPalette::Inactive, KColorScheme::View); return scheme.foreground(KColorScheme::NegativeText).color(); } default: break; } return QVariant(); } Qt::ItemFlags PyKrita::Engine::flags(const QModelIndex& index) const { Q_ASSERT("Sanity check" && index.row() < m_plugins.size()); Q_ASSERT("Sanity check" && index.column() < Column::LAST__); int result = Qt::ItemIsSelectable; if (index.column() == Column::NAME) result |= Qt::ItemIsUserCheckable; // Disable to select/check broken modules if (!m_plugins[index.row()].isBroken()) result |= Qt::ItemIsEnabled; return static_cast(result); } bool PyKrita::Engine::setData(const QModelIndex& index, const QVariant& value, const int role) { Q_ASSERT("Sanity check" && index.row() < m_plugins.size()); if (role == Qt::CheckStateRole) { Q_ASSERT("Sanity check" && !m_plugins[index.row()].isBroken()); const bool enabled = value.toBool(); m_plugins[index.row()].m_enabled = enabled; if (enabled) loadModule(index.row()); else unloadModule(index.row()); } return true; } QStringList PyKrita::Engine::enabledPlugins() const { /// \todo \c std::transform + lambda or even better to use /// filtered and transformed view from boost QStringList result; Q_FOREACH(const PluginState & plugin, m_plugins) if (plugin.isEnabled()) { result.append(plugin.m_pythonPlugin.name()); } return result; } void PyKrita::Engine::readGlobalPluginsConfiguration() { Python py = Python(); PyDict_Clear(m_configuration); KConfig config(CONFIG_FILE, KConfig::SimpleConfig); config.sync(); py.updateDictionaryFromConfiguration(m_configuration, &config); } void PyKrita::Engine::saveGlobalPluginsConfiguration() { Python py = Python(); KConfig config(CONFIG_FILE, KConfig::SimpleConfig); py.updateConfigurationFromDictionary(&config, m_configuration); config.sync(); } bool PyKrita::Engine::isPythonPluginUsable(const PyPlugin *pythonPlugin) { dbgScript << "Got Krita/PythonPlugin: " << pythonPlugin->name() << ", module-path=" << pythonPlugin->library() ; // Make sure mandatory properties are here if (pythonPlugin->name().isEmpty()) { dbgScript << "Ignore desktop file w/o a name"; return false; } if (pythonPlugin->library().isEmpty()) { dbgScript << "Ignore desktop file w/o a module to import"; return false; } return true; } bool PyKrita::Engine::setModuleProperties(PluginState& plugin) { // Find the module: // 0) try to locate directory based plugin first QString rel_path = plugin.moduleFilePathPart(); rel_path = rel_path + "/" + "__init__.py"; dbgScript << "Finding Pyrhon module with rel_path:" << rel_path; QString module_path = KoResourcePaths::findResource("pythonscripts", rel_path); dbgScript << "module_path:" << module_path; if (module_path.isEmpty()) { // 1) Nothing found, then try file based plugin rel_path = plugin.moduleFilePathPart() + ".py"; dbgScript << "Finding Pyrhon module with rel_path:" << rel_path; module_path = KoResourcePaths::findResource("pythonscripts", rel_path); dbgScript << "module_path:" << module_path; } else { plugin.m_isDir = true; } // Is anything found at all? if (module_path.isEmpty()) { plugin.m_broken = true; plugin.m_errorReason = i18nc( "@info:tooltip" , "Unable to find the module specified %1" , plugin.m_pythonPlugin.library() ); dbgScript << "Cannot load module:" << plugin.m_errorReason; return false; } dbgScript << "Found module path:" << module_path; return true; } QPair PyKrita::Engine::parseDependency(const QString& d) { // Check if dependency has package info attached const int pnfo = d.indexOf('('); if (pnfo != -1) { QString dependency = d.mid(0, pnfo); QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed(); dbgScript << "Desired version spec [" << dependency << "]:" << version_str; version_checker checker = version_checker::fromString(version_str); if (!(checker.isValid() && d.endsWith(')'))) { dbgScript << "Invalid version spec " << d; QString reason = i18nc( "@info:tooltip" , "

Specified version has invalid format for dependency %1: " "%2. Skipped

" , dependency , version_str ); return qMakePair(reason, version_checker()); } return qMakePair(dependency, checker); } return qMakePair(d, version_checker(version_checker::undefined)); } PyKrita::version PyKrita::Engine::tryObtainVersionFromTuple(PyObject* version_obj) { Q_ASSERT("Sanity check" && version_obj); if (PyTuple_Check(version_obj) == 0) return version::invalid(); int version_info[3] = {0, 0, 0}; for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) { PyObject* v = PyTuple_GetItem(version_obj, i); if (v && PyLong_Check(v)) version_info[i] = PyLong_AsLong(v); else version_info[i] = -1; } if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1) return version(version_info[0], version_info[1], version_info[2]); return version::invalid(); } /** * Try to parse version string as a simple triplet X.Y.Z. * * \todo Some modules has letters in a version string... * For example current \c pytz version is \e "2013d". */ PyKrita::version PyKrita::Engine::tryObtainVersionFromString(PyObject* version_obj) { Q_ASSERT("Sanity check" && version_obj); if (!Python::isUnicode(version_obj)) return version::invalid(); QString version_str = Python::unicode(version_obj); if (version_str.isEmpty()) return version::invalid(); return version::fromString(version_str); } /** * Collect dependencies and check them. To do it * just try to import a module... when unload it ;) * * \c X-Python-Dependencies property of \c .desktop file has the following format: * python-module(version-info), where python-module * a python module name to be imported, version-spec * is a version triplet delimited by dots, possible w/ leading compare * operator: \c =, \c <, \c >, \c <=, \c >= */ void PyKrita::Engine::verifyDependenciesSetStatus(PluginState& plugin) { QStringList dependencies = plugin.m_pythonPlugin.property("X-Python-Dependencies").toStringList(); #if PY_MAJOR_VERSION < 3 { // Try to get Py2 only dependencies QStringList py2_dependencies = plugin.m_service->property("X-Python-2-Dependencies").toStringList(); dependencies.append(py2_dependencies); } #endif Python py = Python(); QString reason = i18nc("@info:tooltip", "Dependency check"); Q_FOREACH(const QString & d, dependencies) { QPair info_pair = parseDependency(d); version_checker& checker = info_pair.second; if (!checker.isValid()) { plugin.m_broken = true; reason += info_pair.first; continue; } dbgScript << "Try to import dependency module/package:" << d; // Try to import a module const QString& dependency = info_pair.first; PyObject* module = py.moduleImport(PQ(dependency)); if (module) { if (checker.isEmpty()) { // Need to check smth? dbgScript << "No version to check, just make sure it's loaded:" << dependency; Py_DECREF(module); continue; } // Try to get __version__ from module // See PEP396: http://www.python.org/dev/peps/pep-0396/ PyObject* version_obj = py.itemString("__version__", PQ(dependency)); if (!version_obj) { dbgScript << "No __version__ for " << dependency << "[" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback() ; plugin.m_unstable = true; reason += i18nc( "@info:tooltip" , "

Failed to check version of dependency %1: " "Module do not have PEP396 __version__ attribute. " "It is not disabled, but behaviour is unpredictable...

" , dependency ); } // PEP396 require __version__ to tuple of integers... try it! version dep_version = tryObtainVersionFromTuple(version_obj); if (!dep_version.isValid()) // Second attempt: some "bad" modules have it as a string dep_version = tryObtainVersionFromString(version_obj); // Did we get it? if (!dep_version.isValid()) { // Dunno what is this... Giving up! dbgScript << "***: Can't parse module version for" << dependency; plugin.m_unstable = true; reason += i18nc( "@info:tooltip" , "

%1: Unexpected module's version format" , dependency ); } else if (!checker(dep_version)) { dbgScript << "Version requirement check failed [" << plugin.m_pythonPlugin.name() << "] for " << dependency << ": wanted " << checker.operationToString() << QString(checker.required()) << ", but found" << QString(dep_version) ; plugin.m_broken = true; reason += i18nc( "@info:tooltip" , "

%1: No suitable version found. " "Required version %2 %3, but found %4

" , dependency , checker.operationToString() , QString(checker.required()) , QString(dep_version) ); } // Do not need this module anymore... Py_DECREF(module); } else { dbgScript << "Load failure [" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback(); plugin.m_broken = true; reason += i18nc( "@info:tooltip" , "

Failure on module load %1:

%2
" , dependency , py.lastTraceback() ); } } if (plugin.isBroken() || plugin.isUnstable()) { plugin.m_errorReason = reason; } } void PyKrita::Engine::scanPlugins() { m_plugins.clear(); // Clear current state. QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop"); qDebug() << desktopFiles; Q_FOREACH(const QString &desktopFile, desktopFiles) { QSettings s(desktopFile, QSettings::IniFormat); s.beginGroup("Desktop Entry"); if (s.value("ServiceTypes").toString() == "Krita/PythonPlugin") { PyPlugin pyplugin; pyplugin.m_comment = s.value("Comment").toString(); pyplugin.m_name = s.value("Name").toString(); pyplugin.m_libraryPath = s.value("X-KDE-Library").toString(); pyplugin.m_properties["X-Python-2-Compatible"] = s.value("X-Python-2-Compatible", false).toBool(); if (!isPythonPluginUsable(&pyplugin)) { dbgScript << pyplugin.name() << "is not usable"; continue; } PluginState pluginState; pluginState.m_pythonPlugin = pyplugin; if (!setModuleProperties(pluginState)) { dbgScript << "Cannot load" << pyplugin.name() << ": broken" << pluginState.isBroken() << "because:" << pluginState.errorReason(); continue; } verifyDependenciesSetStatus(pluginState); m_plugins.append(pluginState); } } } void PyKrita::Engine::setEnabledPlugins(const QStringList& enabled_plugins) { for (int i = 0; i < m_plugins.size(); ++i) { m_plugins[i].m_enabled = enabled_plugins.indexOf(m_plugins[i].m_pythonPlugin.name()) != -1; } } void PyKrita::Engine::tryLoadEnabledPlugins() { for (int i = 0; i < m_plugins.size(); ++i) { dbgScript << "Trying to load plugin" << m_plugins[i].pythonModuleName() << ". Enabled:" << m_plugins[i].isEnabled() << ". Broken: " << m_plugins[i].isBroken(); if (!m_plugins[i].isBroken()) { m_plugins[i].m_enabled = true; loadModule(i); } } } void PyKrita::Engine::loadModule(const int idx) { Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size()); PluginState& plugin = m_plugins[idx]; Q_ASSERT( "Why to call loadModule() for disabled/broken plugin?" && plugin.isEnabled() && !plugin.isBroken() ); QString module_name = plugin.pythonModuleName(); dbgScript << "Loading module: " << module_name; Python py = Python(); // Get 'plugins' key from 'pykrita' module dictionary. // Every entry has a module name as a key and 2 elements tuple as a value PyObject* plugins = py.itemString("plugins"); Q_ASSERT( "'plugins' dict expected to be alive, otherwise code review required!" && plugins ); PyObject* module = py.moduleImport(PQ(module_name)); if (module) { // Move just loaded module to the dict const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module); Q_ASSERT("expected successful insertion" && ins_result == 0); Py_DECREF(module); // Handle failure in release mode. if (ins_result == 0) { // Initialize the module from Python's side PyObject* const args = Py_BuildValue("(s)", PQ(module_name)); PyObject* result = py.functionCall("_pluginLoaded", Python::PYKRITA_ENGINE, args); Py_DECREF(args); if (result) { dbgScript << "\t" << "success!"; return; } } plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure"); } else { plugin.m_errorReason = i18nc( "@info:tooltip" - , "Module not loaded:%1" - , py.lastTraceback() + , "Module not loaded:
%1" + , py.lastTraceback().replace("\n", "
") ); } plugin.m_broken = true; warnScript << "Error loading plugin" << module_name; } void PyKrita::Engine::unloadModule(int idx) { Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size()); PluginState& plugin = m_plugins[idx]; Q_ASSERT("Why to call unloadModule() for broken plugin?" && !plugin.isBroken()); dbgScript << "Unloading module: " << plugin.pythonModuleName(); Python py = Python(); // Get 'plugins' key from 'pykrita' module dictionary PyObject* plugins = py.itemString("plugins"); Q_ASSERT( "'plugins' dict expected to be alive, otherwise code review required!" && plugins ); PyObject* const args = Py_BuildValue("(s)", PQ(plugin.pythonModuleName())); py.functionCall("_pluginUnloading", Python::PYKRITA_ENGINE, args); Py_DECREF(args); // This will just decrement a reference count for module instance PyDict_DelItemString(plugins, PQ(plugin.pythonModuleName())); // Remove the module also from 'sys.modules' dict to really unload it, // so if reloaded all @init actions will work again! PyObject* sys_modules = py.itemString("modules", "sys"); Q_ASSERT("Sanity check" && sys_modules); PyDict_DelItemString(sys_modules, PQ(plugin.pythonModuleName())); } // krita: space-indent on; indent-width 4; #undef PYKRITA_INIT diff --git a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py index bbde02a340..f2b12cf721 100644 --- a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py +++ b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py @@ -1,412 +1,412 @@ # -*- coding: utf-8 -*- """ 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. """ """ Mini Kross - a scripting solution inspired by Kross (http://kross.dipe.org/) Technically this is one of the most important modules in Scripter. Via the Qt meta object system it provides access to unwrapped objects. This code uses a lot of metaprogramming magic. To fully understand it, you have to know about metaclasses in Python """ import sys import sip from PyQt5.QtCore import QVariant, QMetaObject, Q_RETURN_ARG, Q_ARG, QObject, Qt, QMetaMethod, pyqtSignal from PyQt5.QtGui import QBrush, QFont, QImage, QPalette, QPixmap from PyQt5.QtWidgets import qApp variant_converter = { "QVariantList": lambda v: v.toList(v), - "QVariantMap": lambda v: toPyObject(v), - "QPoint": lambda v: v.toPoint(), - "str": lambda v: v.toString(), - "int": lambda v: v.toInt()[0], - "double": lambda v: v.toDouble()[0], - "char": lambda v: v.toChar(), - "QByteArray": lambda v: v.toByteArray(), - "QPoint": lambda v: v.toPoint(), - "QPointF": lambda v: v.toPointF(), - "QSize": lambda v: v.toSize(), - "QLine": lambda v: v.toLine(), - "QStringList": lambda v: v.toStringList(), - "QTime": lambda v: v.toTime(), - "QDateTime": lambda v: v.toDateTime(), - "QDate": lambda v: v.toDate(), - "QLocale": lambda v: v.toLocale(), - "QUrl": lambda v: v.toUrl(), - "QRect": lambda v: v.toRect(), - "QBrush": lambda v: QBrush(v), - "QFont": lambda v: QFont(v), - "QPalette": lambda v: QPalette(v), - "QPixmap": lambda v: QPixmap(v), - "QImage": lambda v: QImage(v), - "bool": lambda v: v.toBool(), - "QObject*": lambda v: wrap_variant_object(v), - "QWidget*": lambda v: wrap_variant_object(v), - "ActionMap": lambda v: int(v.count()) + "QVariantMap": lambda v: toPyObject(v), + "QPoint": lambda v: v.toPoint(), + "str": lambda v: v.toString(), + "int": lambda v: v.toInt()[0], + "double": lambda v: v.toDouble()[0], + "char": lambda v: v.toChar(), + "QByteArray": lambda v: v.toByteArray(), + "QPoint": lambda v: v.toPoint(), + "QPointF": lambda v: v.toPointF(), + "QSize": lambda v: v.toSize(), + "QLine": lambda v: v.toLine(), + "QStringList": lambda v: v.toStringList(), + "QTime": lambda v: v.toTime(), + "QDateTime": lambda v: v.toDateTime(), + "QDate": lambda v: v.toDate(), + "QLocale": lambda v: v.toLocale(), + "QUrl": lambda v: v.toUrl(), + "QRect": lambda v: v.toRect(), + "QBrush": lambda v: QBrush(v), + "QFont": lambda v: QFont(v), + "QPalette": lambda v: QPalette(v), + "QPixmap": lambda v: QPixmap(v), + "QImage": lambda v: QImage(v), + "bool": lambda v: v.toBool(), + "QObject*": lambda v: wrap_variant_object(v), + "QWidget*": lambda v: wrap_variant_object(v), + "ActionMap": lambda v: int(v.count()) } def wrap_variant_object(variant): """ convert a QObject or a QWidget to its wrapped superclass """ o = Krita.fromVariant(variant) return wrap(o, True) def from_variant(variant): """ convert a QVariant to a Python value """ # Check whether it's really a QVariant if hasattr(variant, '__type__') and not (variant is None or variant.type() is None): typeName = variant.typeName() convert = variant_converter.get(typeName) if not convert: raise ValueError("Could not convert value to %s" % typeName) else: v = convert(variant) return v # Give up and return return variant def convert_value(value): """ Convert a given value, upcasting to the highest QObject-based class if possible, unpacking lists and dicts. """ # Check whether it's a dict: if so, convert the keys/values if hasattr(value, '__class__') and issubclass(value.__class__, dict) and len(value) > 0: return {convert_value(k): convert_value(v) for k, v in value.items()} # Check whether it's a list: if so, convert the values if hasattr(value, '__class__') and issubclass(value.__class__, list) and len(value) > 0: return [convert_value(v) for v in value] if isinstance(value, str): # prefer Python strings return str(value) elif isinstance(value, PyQtClass): # already wrapped return value # Check whether it's a QObject if hasattr(value, '__class__') and issubclass(value.__class__, QObject): return wrap(value, True) if hasattr(value, '__type__') and not (value is None or value.type() is None): return from_variant(value) return value qtclasses = {} def wrap(obj, force=False): """ If a class is not known by PyQt it will be automatically casted to a known wrapped super class. But that limits access to methods and propperties of this super class. So instead this functions returns a wrapper class (PyQtClass) which queries the metaObject and provides access to all slots and all properties. """ if isinstance(obj, str): # prefer Python strings return str(obj) elif isinstance(obj, PyQtClass): # already wrapped return obj elif obj and isinstance(obj, QObject): if force or obj.__class__.__name__ != obj.metaObject().className(): # Ah this is an unwrapped class obj = create_pyqt_object(obj) return obj def unwrap(obj): """ if wrapped returns the wrapped object """ if hasattr(obj, "qt"): obj = obj.qt return obj def is_qobject(obj): """ checks if class or wrapped class is a subclass of QObject """ if hasattr(obj, "__bases__") and issubclass(unwrap(obj), QObject): return True else: return False def is_scripter_child(qobj): """ walk up the object tree until Scripter or the root is found """ found = False p = qobj.parent() while p and not found: if str(p.objectName()) == "Krita": found = True break else: p = p.parent() return found class Error(Exception): """ Base error classed. Catch this to handle exceptions comming from C++ """ class PyQtClass(object): """ Base class """ def __init__(self, instance): self._instance = instance def __del__(self): """ If this object is deleted it should also delete the wrapped object if it was created explicitly for this use. """ qobj = self._instance if is_scripter_child(qobj): if len(qobj.children()): print("Cannot delete", qobj, "because it has child objects") sip.delete(qobj) def setProperty(self, name, value): self._instance.setProperty(name, value) def getProperty(self, name): return wrap(self._instance.property(name)) def propertyNames(self): return list(self.__class__.__properties__.keys()) def dynamicPropertyNames(self): return self._instance.dynamicPropertyNames() def metaObject(self): return self._instance.metaObject() def connect(self, signal, slot): getattr(self._instance, signal).connect(slot) def disconnect(self, signal, slot): getattr(self._instance, signal).disconnect(slot) def parent(self): return wrap(self._instance.parent()) def children(self): return [wrap(c) for c in self._instance.children()] @property def qt(self): return self._instance def __getitem__(self, key): if isinstance(key, int): length = getattr(self, "length", None) if length is not None: # array protocol try: return getattr(self, str(key)) except AttributeError as e: raise IndexError(key) else: return self.children()[key] else: return getattr(self, key) def __getattr__(self, name): # Make named child objects available as attributes like QtQml # Check whether the object is in the QObject hierarchy for child in self._instance.children(): if str(child.objectName()) == name: obj = wrap(child) # Save found object for faster lookup setattr(self, name, obj) return obj # Check whether it's a property v = self._instance.property(name) return convert_value(v) @property def __members__(self): """ This method is for introspection. Using dir(thispyqtclass_object) returns a list of all children, methods, properties and dynamic properties. """ names = list(self.__dict__.keys()) for c in self._instance.children(): child_name = str(c.objectName()) if child_name: names.append(child_name) for pn in self._instance.dynamicPropertyNames(): names.append(str(pn)) return names def __enter__(self): print("__enter__", self) def __exit__(self, exc_type, exc_value, traceback): print("__exit__", self, exc_type, exc_value, traceback) class PyQtProperty(object): # slots for more speed __slots__ = ["meta_property", "name", "__doc__", "read_only"] def __init__(self, meta_property): self.meta_property = meta_property self.name = meta_property.name() self.read_only = not meta_property.isWritable() self.__doc__ = "%s is a %s%s" % ( self.name, meta_property.typeName(), - self.read_only and " (read-only)" or "" + self.read_only and " (read-only)" or "" ) def get(self, obj): return convert_value(self.meta_property.read(obj._instance)) def set(self, obj, value): self.meta_property.write(obj._instance, value) class PyQtMethod(object): __slots__ = ["meta_method", "name", "args", "returnType", "__doc__"] def __init__(self, meta_method): self.meta_method = meta_method self.name, args = str(meta_method.methodSignature(), encoding="utf-8").split("(", 1) self.args = args[:-1].split(",") self.returnType = str(meta_method.typeName()) types = [str(t, encoding="utf-8") for t in meta_method.parameterTypes()] names = [str(n, encoding="utf-8") or "arg%i" % (i + 1) for i, n in enumerate(meta_method.parameterNames())] params = ", ".join("%s %s" % (t, n) for n, t in zip(types, names)) self.__doc__ = "%s(%s)%s" % ( self.name, params, - self.returnType and (" -> %s" % self.returnType) or "" + self.returnType and (" -> %s" % self.returnType) or "" ) def instancemethod(self): def wrapper(obj, *args): qargs = [Q_ARG(t, v) for t, v in zip(self.args, args)] invoke_args = [obj._instance, self.name] invoke_args.append(Qt.DirectConnection) rtype = self.returnType if rtype: invoke_args.append(Q_RETURN_ARG(rtype)) invoke_args.extend(qargs) try: result = QMetaObject.invokeMethod(*invoke_args) except RuntimeError as e: raise TypeError( "%s.%s(%r) call failed: %s" % (obj, self.name, args, e)) return wrap(result) wrapper.__doc__ = self.__doc__ return wrapper # Cache on-the-fly-created classes for better speed pyqt_classes = {} def create_pyqt_class(metaobject): class_name = str(metaobject.className()) cls = pyqt_classes.get(class_name) if cls: return cls attrs = {} properties = attrs["__properties__"] = {} for i in range(metaobject.propertyCount()): prop = PyQtProperty(metaobject.property(i)) prop_name = str(prop.name) if prop.read_only: properties[prop_name] = attrs[prop_name] = property(prop.get, doc=prop.__doc__) else: properties[prop_name] = attrs[prop_name] = property( prop.get, prop.set, doc=prop.__doc__) methods = attrs["__methods__"] = {} signals = attrs["__signals__"] = {} for i in range(metaobject.methodCount()): meta_method = metaobject.method(i) if meta_method.methodType() != QMetaMethod.Signal: method = PyQtMethod(meta_method) method_name = method.name if method_name in attrs: # There is already a property with this name # So append an underscore method_name += "_" instance_method = method.instancemethod() instance_method.__doc__ = method.__doc__ methods[method_name] = attrs[method_name] = instance_method else: method_name = meta_method.name() signal_attrs = [] properties[bytes(method_name).decode('ascii')] = pyqtSignal(meta_method.parameterTypes()) # Dynamically create a class with a base class and a dictionary cls = type(class_name, (PyQtClass,), attrs) pyqt_classes[class_name] = cls return cls def create_pyqt_object(obj): """ Wrap a QObject and make all slots and properties dynamically available. @type obj: QObject @param obj: an unwrapped QObject @rtype: PyQtClass object @return: dynamically created object with all available properties and slots This is probably the only function you need from this module. Everything else are helper functions and classes. """ cls = create_pyqt_class(obj.metaObject()) return cls(obj) diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py index adc88aa00b..b15881b0b3 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py @@ -1,475 +1,475 @@ from __future__ import print_function import sys import traceback import re from PyQt5.QtCore import QObject, Qt from PyQt5.QtGui import QTextCursor from PyQt5.QtWidgets import qApp, QApplication, QPlainTextEdit from highlighter import PythonHighlighter, QtQmlHighlighter from PyQt5.QtQml import ( QScriptEngine, QScriptValue, QScriptValueIterator) class OutputWidget(QPlainTextEdit): def __init__(self, parent=None, readonly=True, max_rows=1000, echo=True): QPlainTextEdit.__init__(self, parent) self.echo = echo self.setReadOnly(readonly) self.document().setMaximumBlockCount(max_rows) self.attach() def attach(self): sys.stdout = sys.stderr = self def __del__(self): self.detach() def detach(self): sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ def write(self, s): if self.echo: sys.__stdout__.write(s) doc = self.document() cursor = QTextCursor(doc) cursor.clearSelection() cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) cursor.insertText(s) cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) cursor.clearSelection() self.ensureCursorVisible() qApp.processEvents() def writelines(self, lines): self.write("\n".join(lines)) class ConsoleWidget(OutputWidget): def __init__(self, parent=None, ps1="?", ps2=">"): OutputWidget.__init__(self, parent, readonly=False) self.setTabChangesFocus(False) self.ps1 = ps1 self.ps2 = ps2 self.history_index = 0 self.history = [""] self.tab_state = -1 print(self.ps1, end='') def focusInEvent(self, event): self.attach() OutputWidget.focusInEvent(self, event) def mousePressEvent(self, event): self.setFocus() def push(self, line): return True def keyPressEvent(self, event): def remove_line(): cursor = self.textCursor() cursor.select(QTextCursor.BlockUnderCursor) cursor.removeSelectedText() key = event.key() modifiers = event.modifiers() l = len(self.ps1) line = unicode(self.document().end().previous().text()) ps1orps2, line = line[:l - 1], line[l:] if not key in [Qt.Key_Tab, Qt.Key_Backtab] and \ len(event.text()): self.tab_state = -1 if key == Qt.Key_Up: if self.history_index + 1 < len(self.history): self.history_index += 1 remove_line() print() print(ps1orps2, self.history[self.history_index], end='') elif key == Qt.Key_Down: if self.history_index > 0: self.history_index -= 1 remove_line() print() print(ps1orps2, self.history[self.history_index], end='') elif key == Qt.Key_Tab: if modifiers & Qt.ControlModifier: print(" " * 4, end='') else: self.tab_state += 1 remove_line() print() print(ps1orps2, end='') print(self.completer.complete(line, self.tab_state) or line, end='') elif key == Qt.Key_Backtab: if self.tab_state >= 0: self.tab_state -= 1 remove_line() print() print(ps1orps2, end='') print(self.completer.complete(line, self.tab_state) or line, end='') elif key in [Qt.Key_Backspace, Qt.Key_Left]: if self.textCursor().columnNumber() > len(ps1orps2) + 1: return OutputWidget.keyPressEvent(self, event) elif key == Qt.Key_Return: self.moveCursor(QTextCursor.EndOfLine, QTextCursor.MoveAnchor) print() if self.push(line): print(self.ps2, end='') else: print(self.ps1, end='') if line and line != self.history[self.history_index]: self.history.insert(1, line) self.history_index = 0 else: return OutputWidget.keyPressEvent(self, event) class PythonInterpreter(object): def __init__(self, name="", locals=None): self.name = name self.locals = locals or {} self.locals["__name__"] = self.name self.lines = [] def run(self, source, locals=None): if locals == None: locals = self.locals code = compile(source, self.name, "exec") try: exec(code, locals) except: self.showtraceback() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() except: pass def push(self, line): if self.lines: if line: self.lines.append(line) return 1 # want more! else: line = "\n".join(self.lines) + "\n" else: if not line: return 0 try: code = compile(line, self.name, "single") self.lines = [] except SyntaxError as why: if why[0] == "unexpected EOF while parsing": self.lines.append(line) return 1 # want more! else: self.showtraceback() except: self.showtraceback() else: try: exec(code, self.locals) except: self.showtraceback() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() except: pass return 0 def showtraceback(self): self.lines = [] if sys.exc_info()[0] == SyntaxError: # and len(sys.exc_value) == 2: print(" File \"%s\", line %d" % (self.name, sys.exc_value[1][1])) print(" " * (sys.exc_value[1][2] + 2) + "^") print(str(sys.exc_info()[0]) + ":", sys.exc_value[0]) else: traceback.print_tb(sys.exc_info()[2], None) print(sys.exc_type.__name__ + ":", sys.exc_info()[1]) class PythonCompleter(object): def __init__(self, namespace): self.namespace = namespace def complete(self, text, state): if state == 0: if "." in text: self.matches = self.attr_matches(text) else: self.matches = self.global_matches(text) try: return self.matches[state] except IndexError: return None def global_matches(self, text): import keyword import __builtin__ matches = [] n = len(text) for list in [keyword.kwlist, __builtin__.__dict__, self.namespace]: for word in list: if word[:n] == text and word != "__builtins__": matches.append(word) return matches def attr_matches(self, text): def get_class_members(cls): ret = dir(cls) if hasattr(cls, '__bases__'): for base in cls.__bases__: ret = ret + get_class_members(base) return ret import re m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) if not m: return expr, attr = m.group(1, 3) object = eval(expr, self.namespace) words = dir(object) if hasattr(object, '__class__'): words.append('__class__') words = words + get_class_members(object.__class__) matches = [] n = len(attr) for word in words: if word[:n] == attr and word != "__builtins__": matches.append("%s.%s" % (expr, word)) return matches class PythonConsole(ConsoleWidget): def __init__(self, parent=None, namespace=None): ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ") self.highlighter = PythonHighlighter(self) self.inter = PythonInterpreter(locals=namespace) self.namespace = self.inter.locals self.completer = PythonCompleter(self.namespace) # print("Python", sys.version) # print("Autocomplete with (Shift+)Tab, insert spaces with Ctrl+Tab") self.push("pass") def push(self, line): return self.inter.push(line) def clear(self): doc = self.document() doc.setPlainText(self.ps1) class QtQmlInterpreter(object): def __init__(self, locals): self.locals = locals self.engine = self.newEngine() self.code = "" self.state = 0 def newEngine(self): engine = QScriptEngine() ns = engine.globalObject() for name, value in self.locals.items(): if isinstance(value, QObject): value = engine.newQObject(value) elif callable(value): value = engine.newFunction(value) ns.setProperty(name, value) return engine def execute(self, code): self.execute_code(code, self.engine) def execute_code(self, code, engine=None): engine = engine or self.newEngine() result = engine.evaluate(code) try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() except: pass if engine.hasUncaughtException(): bt = engine.uncaughtExceptionBacktrace() print("Traceback:") print("\n".join([" %s" % l for l in list(bt)])) print(engine.uncaughtException().toString()) else: if not result.isUndefined(): print(result.toString()) def push(self, line): if not line.strip(): return self.state self.code = self.code + line + "\n" if self.engine.canEvaluate(self.code): self.execute(self.code) self.code = "" self.state = 0 else: self.state = 1 return self.state js_words = [ 'break', - 'for', - 'throw', - 'case', - 'function', - 'try', - 'catch', - 'if', - 'typeof', - 'continue', - 'in', - 'var', - 'default', - 'instanceof', - 'void', - 'delete', - 'new', - 'undefined', - 'do', - 'return', - 'while', - 'else', - 'switch', - 'with', - 'finally', - 'this', - 'NaN', - 'Infinity', - 'undefined', - 'print', - 'parseInt', - 'parseFloat', - 'isNaN', - 'isFinite', - 'decodeURI', - 'decodeURIComponent', - 'encodeURI', - 'encodeURIComponent', - 'escape', - 'unescape', - 'version', - 'gc', - 'Object', - 'Function', - 'Number', - 'Boolean', - 'String', - 'Date', - 'Array', - 'RegExp', - 'Error', - 'EvalError', - 'RangeError', - 'ReferenceError', - 'SyntaxError', - 'TypeError', - 'URIError', - 'eval', - 'Math', - 'Enumeration', - 'Variant', - 'QObject', - 'QMetaObject'] + 'for', + 'throw', + 'case', + 'function', + 'try', + 'catch', + 'if', + 'typeof', + 'continue', + 'in', + 'var', + 'default', + 'instanceof', + 'void', + 'delete', + 'new', + 'undefined', + 'do', + 'return', + 'while', + 'else', + 'switch', + 'with', + 'finally', + 'this', + 'NaN', + 'Infinity', + 'undefined', + 'print', + 'parseInt', + 'parseFloat', + 'isNaN', + 'isFinite', + 'decodeURI', + 'decodeURIComponent', + 'encodeURI', + 'encodeURIComponent', + 'escape', + 'unescape', + 'version', + 'gc', + 'Object', + 'Function', + 'Number', + 'Boolean', + 'String', + 'Date', + 'Array', + 'RegExp', + 'Error', + 'EvalError', + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'URIError', + 'eval', + 'Math', + 'Enumeration', + 'Variant', + 'QObject', + 'QMetaObject'] class QtQmlCompleter(object): def __init__(self, engine): self.engine = engine def complete(self, text, state): if state == 0: if "." in text: self.matches = self.attr_matches(text) else: self.matches = self.global_matches(text) try: return self.matches[state] except IndexError: return None def attr_matches(self, text): return [] def iter_obj(self, obj): it = QScriptValueIterator(self.engine.globalObject()) while it.hasNext(): yield str(it.name()) it.next() def global_matches(self, text): words = list(self.iter_obj(self.engine.globalObject())) words.extend(js_words) l = [] n = len(text) for w in words: if w[:n] == text: l.append(w) return l class QtQmlConsole(ConsoleWidget): def __init__(self, parent=None, namespace=None): ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ") self.highlighter = QtQmlHighlighter(self) namespace = namespace or {} def console_print(context, engine): for i in range(context.argumentCount()): print(context.argument(i).toString(), end='') print() return QScriptValue() def dir_context(context, engine): if context.argumentCount() == 0: obj = context.thisObject() else: obj = context.argument(0) l = [] it = QScriptValueIterator(obj) while it.hasNext(): it.next() l.append(str(it.name())) return QScriptValue(engine, repr(l)) namespace["print"] = console_print namespace["dir"] = dir_context namespace["Application"] = qApp try: namespace["Scripter"] = Scripter.qt except: pass self.inter = QtQmlInterpreter(namespace) self.completer = QtQmlCompleter(self.inter.engine) def push(self, line): return self.inter.push(line) if __name__ == "__main__": app = QApplication(sys.argv) o = QtQmlConsole() # o = PythonConsole() o.resize(640, 480) o.attach() o.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py index ded5039429..71da7afe72 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py @@ -1,390 +1,390 @@ # Ported from KoDockWidgetTitleBar.cpp which is part of KOffice # Copyright (c) 2007 Marijn Kruisselbrink # Copyright (C) 2007 Thomas Zander # The code is distributed under GPL 2 or any later version import os from PyQt5.QtCore import QPoint, QSize, Qt, QRect, QTimer from PyQt5.QtGui import (QIcon, QPainter) from PyQt5.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDockWidget, QHBoxLayout, QLayout, QMainWindow, QPushButton, QStyle, QStyleOptionDockWidget, QStyleOptionToolButton, QStylePainter, QWidget) import dockwidget_icons def hasFeature(dockwidget, feature): return dockwidget.features() & feature == feature class DockWidgetTitleBarButton(QAbstractButton): def __init__(self, titlebar): QAbstractButton.__init__(self, titlebar) self.setFocusPolicy(Qt.NoFocus) def sizeHint(self): self.ensurePolished() margin = self.style().pixelMetric(QStyle.PM_DockWidgetTitleBarButtonMargin, None, self) if self.icon().isNull(): return QSize(margin, margin) iconSize = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self) pm = self.icon().pixmap(iconSize) return QSize(pm.width() + margin, pm.height() + margin) def enterEvent(self, event): if self.isEnabled(): self.update() QAbstractButton.enterEvent(self, event) def leaveEvent(self, event): if self.isEnabled(): self.update() QAbstractButton.leaveEvent(self, event) def paintEvent(self, event): p = QPainter(self) r = self.rect() opt = QStyleOptionToolButton() opt.init(self) opt.state |= QStyle.State_AutoRaise if self.isEnabled() and self.underMouse() and \ not self.isChecked() and not self.isDown(): opt.state |= QStyle.State_Raised if self.isChecked(): opt.state |= QStyle.State_On if self.isDown(): opt.state |= QStyle.State_Sunken self.style().drawPrimitive( QStyle.PE_PanelButtonTool, opt, p, self) opt.icon = self.icon() opt.subControls = QStyle.SubControls() opt.activeSubControls = QStyle.SubControls() opt.features = QStyleOptionToolButton.None opt.arrowType = Qt.NoArrow size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self) opt.iconSize = QSize(size, size) self.style().drawComplexControl(QStyle.CC_ToolButton, opt, p, self) class DockWidgetTitleBar(QWidget): # XXX: support QDockWidget.DockWidgetVerticalTitleBar feature def __init__(self, dockWidget): QWidget.__init__(self, dockWidget) self.openIcon = QIcon(":arrow-down.png") self.closeIcon = QIcon(":arrow-right.png") self.pinIcon = QIcon(":pin.png") q = dockWidget self.floatButton = DockWidgetTitleBarButton(self) self.floatButton.setIcon(q.style().standardIcon( QStyle.SP_TitleBarNormalButton, None, q)) self.floatButton.clicked.connect(self.toggleFloating) self.floatButton.setVisible(True) self.closeButton = DockWidgetTitleBarButton(self) self.closeButton.setIcon(q.style().standardIcon( QStyle.SP_TitleBarCloseButton, None, q)) self.closeButton.clicked.connect(dockWidget.close) self.closeButton.setVisible(True) self.collapseButton = DockWidgetTitleBarButton(self) self.collapseButton.setIcon(self.openIcon) self.collapseButton.clicked.connect(self.toggleCollapsed) self.collapseButton.setVisible(True) self.pinButton = DockWidgetTitleBarButton(self) self.pinButton.setIcon(self.pinIcon) self.pinButton.setCheckable(True) self.pinButton.setChecked(True) self.pinButton.clicked.connect(self.togglePinned) self.pinButton.setVisible(True) dockWidget.featuresChanged.connect(self.featuresChanged) self.featuresChanged(0) def minimumSizeHint(self): return self.sizeHint() def sizeHint(self): q = self.parentWidget() mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) fw = q.style().pixelMetric(QStyle.PM_DockWidgetFrameWidth, None, q) closeSize = QSize(0, 0) if self.closeButton: closeSize = self.closeButton.sizeHint() floatSize = QSize(0, 0) if self.floatButton: floatSize = self.floatButton.sizeHint() hideSize = QSize(0, 0) if self.collapseButton: hideSize = self.collapseButton.sizeHint() pinSize = QSize(0, 0) if self.pinButton: pinSize = self.pinButton.sizeHint() buttonHeight = max(max(closeSize.height(), floatSize.height()), hideSize.height(), pinSize.height()) + 2 buttonWidth = closeSize.width() + floatSize.width() + hideSize.width() + pinSize.width() titleFontMetrics = q.fontMetrics() fontHeight = titleFontMetrics.lineSpacing() + 2 * mw height = max(buttonHeight, fontHeight) width = buttonWidth + height + 4 * mw + 2 * fw if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): width, height = height, width return QSize(width, height) def paintEvent(self, event): p = QStylePainter(self) q = self.parentWidget() if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): fw = 1 or q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) titleOpt = QStyleOptionDockWidget() titleOpt.initFrom(q) titleOpt.verticalTitleBar = True titleOpt.rect = QRect( QPoint(fw, fw + mw + self.collapseButton.size().height() + self.pinButton.size().height()), QSize( self.geometry().width() - (fw * 2), - self.geometry().height() - (fw * 2) - - mw - self.collapseButton.size().height() - self.pinButton.size().height())) + self.geometry().height() - (fw * 2) - + mw - self.collapseButton.size().height() - self.pinButton.size().height())) titleOpt.title = q.windowTitle() titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt) else: fw = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) titleOpt = QStyleOptionDockWidget() titleOpt.initFrom(q) titleOpt.rect = QRect( QPoint(fw + mw + self.collapseButton.size().width() + self.pinButton.size().width(), fw), QSize( self.geometry().width() - (fw * 2) - - mw - self.collapseButton.size().width() - self.pinButton.size().width(), - self.geometry().height() - (fw * 2))) + mw - self.collapseButton.size().width() - self.pinButton.size().width(), + self.geometry().height() - (fw * 2))) titleOpt.title = q.windowTitle() titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt) def resizeEvent(self, event): q = self.parentWidget() if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): fh = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 opt = QStyleOptionDockWidget() opt.initFrom(q) opt.verticalTitleBar = True opt.rect = QRect( QPoint(fh, 40), # self.geometry().height() - (fh * 3)), QSize( self.geometry().width() - (fh * 2), - fh * 2)) + fh * 2)) opt.title = q.windowTitle() opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) floatRect = q.style().subElementRect( QStyle.SE_DockWidgetFloatButton, opt, q) if not floatRect.isNull(): self.floatButton.setGeometry(floatRect) closeRect = q.style().subElementRect( QStyle.SE_DockWidgetCloseButton, opt, q) if not closeRect.isNull(): self.closeButton.setGeometry(closeRect) top = fh if not floatRect.isNull(): top = floatRect.x() elif not closeRect.isNull(): top = closeRect.x() size = self.collapseButton.size() if not closeRect.isNull(): size = self.closeButton.size() elif not floatRect.isNull(): size = self.floatButton.size() collapseRect = QRect(QPoint(top, fh), size) self.collapseButton.setGeometry(collapseRect) pinRect = QRect(QPoint(top, fh + collapseRect.height() + 1), size) self.pinButton.setGeometry(pinRect) else: fw = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 opt = QStyleOptionDockWidget() opt.initFrom(q) opt.rect = QRect( QPoint(fw, fw), QSize( self.geometry().width() - (fw * 2), - self.geometry().height() - (fw * 2))) + self.geometry().height() - (fw * 2))) opt.title = q.windowTitle() opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) floatRect = q.style().subElementRect( QStyle.SE_DockWidgetFloatButton, opt, q) if not floatRect.isNull(): self.floatButton.setGeometry(floatRect) closeRect = q.style().subElementRect( QStyle.SE_DockWidgetCloseButton, opt, q) if not closeRect.isNull(): self.closeButton.setGeometry(closeRect) top = fw if not floatRect.isNull(): top = floatRect.y() elif not closeRect.isNull(): top = closeRect.y() size = self.collapseButton.size() if not closeRect.isNull(): size = self.closeButton.size() elif not floatRect.isNull(): size = self.floatButton.size() collapseRect = QRect(QPoint(fw, top), size) self.collapseButton.setGeometry(collapseRect) pinRect = QRect(QPoint(fw + collapseRect.width() + 1, top), size) self.pinButton.setGeometry(pinRect) def setCollapsed(self, collapsed): q = self.parentWidget() if q and q.widget() and q.widget().isHidden() != collapsed: self.toggleCollapsed() def toggleFloating(self): q = self.parentWidget() q.setFloating(not q.isFloating()) def toggleCollapsed(self): q = self.parentWidget() if not q: return q.toggleCollapsed() self.setCollapsedIcon(q.isCollapsed()) def setCollapsedIcon(self, flag): self.collapseButton.setIcon(flag and self.openIcon or self.closeIcon) def togglePinned(self, checked): self.parent().setPinned(checked) def featuresChanged(self, features): q = self.parentWidget() self.closeButton.setVisible(hasFeature(q, QDockWidget.DockWidgetClosable)) self.floatButton.setVisible(hasFeature(q, QDockWidget.DockWidgetFloatable)) # self.resizeEvent(None) class DockMainWidgetWrapper(QWidget): def __init__(self, dockwidget): QWidget.__init__(self, dockwidget) self.widget = None self.hlayout = QHBoxLayout(self) self.hlayout.setSpacing(0) self.hlayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.hlayout) def setWidget(self, widget): self.widget = widget self.widget_height = widget.height self.layout().addWidget(widget) def isCollapsed(self): return self.widget.isVisible() def setCollapsed(self, flag): if not flag: self.old_size = self.size() self.layout().removeWidget(self.widget) self.widget.hide() if hasFeature(self.parent(), QDockWidget.DockWidgetVerticalTitleBar): self.parent().setMaximumWidth(self.parent().width() - self.width()) else: self.parent().setMaximumHeight(self.parent().height() - self.height()) else: self.setFixedSize(self.old_size) self.parent().setMinimumSize(QSize(1, 1)) self.parent().setMaximumSize(QSize(32768, 32768)) self.widget.show() self.layout().addWidget(self.widget) self.setMinimumSize(QSize(1, 1)) self.setMaximumSize(QSize(32768, 32768)) class DockWidget(QDockWidget): def __init__(self, *args): QDockWidget.__init__(self, *args) self.titleBar = DockWidgetTitleBar(self) self.setTitleBarWidget(self.titleBar) self.mainWidget = None self.entered = False self.pinned = True self.shot = False def enterEvent(self, event): self.entered = True if not self.shot and not self.isPinned() and not self.isFloating(): self.shot = True QTimer.singleShot(500, self.autoshow) return QDockWidget.enterEvent(self, event) def leaveEvent(self, event): self.entered = False if not self.shot and not self.isPinned() and not self.isFloating(): self.shot = True QTimer.singleShot(1000, self.autohide) return QDockWidget.leaveEvent(self, event) def autohide(self): self.shot = False if not self.entered: self.setCollapsed(False) def autoshow(self): self.shot = False if self.entered: self.setCollapsed(True) def isPinned(self): return self.pinned def setPinned(self, flag): self.pinned = flag def setWidget(self, widget): self.mainWidget = DockMainWidgetWrapper(self) self.mainWidget.setWidget(widget) QDockWidget.setWidget(self, self.mainWidget) def setCollapsed(self, flag): self.mainWidget.setCollapsed(flag) self.titleBarWidget().setCollapsedIcon(flag) def isCollapsed(self): return self.mainWidget.isCollapsed() def toggleCollapsed(self): self.setCollapsed(not self.isCollapsed()) if __name__ == "__main__": import sys from PyQt5.QtGui import QTextEdit app = QApplication(sys.argv) app.setStyle("qtcurve") win = QMainWindow() dock1 = DockWidget("1st dockwidget", win) dock1.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar) combo = QComboBox(dock1) dock1.setWidget(combo) win.addDockWidget(Qt.LeftDockWidgetArea, dock1) dock2 = DockWidget("2nd dockwidget") dock2.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar) button = QPushButton("Hello, world!", dock2) dock2.setWidget(button) win.addDockWidget(Qt.RightDockWidgetArea, dock2) edit = QTextEdit(win) win.setCentralWidget(edit) win.resize(640, 480) win.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py index e1b34e07f3..079ef581f8 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py @@ -1,189 +1,189 @@ #!/usr/bin/env python """ highlightedtextedit.py A PyQt custom widget example for Qt Designer. Copyright (C) 2006 David Boddie Copyright (C) 2005-2006 Trolltech ASA. All rights reserved. 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 St, Fifth Floor, Boston, MA 02110-1301 USA """ from PyQt5 import QtCore, QtGui class PythonHighlighter(QtGui.QSyntaxHighlighter): keywords = ( "and", "del", "for", "is", "raise", "assert", "elif", "from", "lambda", "return", "break", "else", "global", "not", "try", "class", "except", "if", "or", "while", "continue", "exec", "import", "pass", "yield", "def", "finally", "in", "print" ) def __init__(self, edit): document = edit.document() QtGui.QSyntaxHighlighter.__init__(self, document) base_format = QtGui.QTextCharFormat() base_format.setFont(edit.font()) self.base_format = base_format self.document = document self.updateHighlighter(base_format.font()) def highlightBlock(self, text): self.setCurrentBlockState(0) if text.trimmed().isEmpty(): self.setFormat(0, len(text), self.empty_format) return self.setFormat(0, len(text), self.base_format) startIndex = 0 if self.previousBlockState() != 1: startIndex = self.multiLineStringBegin.indexIn(text) if startIndex > -1: self.highlightRules(text, 0, startIndex) else: self.highlightRules(text, 0, len(text)) while startIndex >= 0: endIndex = self.multiLineStringEnd.indexIn(text, startIndex + len(self.multiLineStringBegin.pattern())) if endIndex == -1: self.setCurrentBlockState(1) commentLength = text.length() - startIndex else: commentLength = endIndex - startIndex + \ self.multiLineStringEnd.matchedLength() self.highlightRules(text, endIndex, len(text)) self.setFormat(startIndex, commentLength, self.multiLineStringFormat) startIndex = self.multiLineStringBegin.indexIn(text, startIndex + commentLength) def highlightRules(self, text, start, finish): for expression, format in self.rules: index = expression.indexIn(text, start) while index >= start and index < finish: length = expression.matchedLength() self.setFormat(index, min(length, finish - index), format) index = expression.indexIn(text, index + length) def updateFonts(self, font): self.base_format.setFont(font) self.empty_format = QtGui.QTextCharFormat(self.base_format) # self.empty_format.setFontPointSize(font.pointSize()/4.0) self.keywordFormat = QtGui.QTextCharFormat(self.base_format) self.keywordFormat.setForeground(QtCore.Qt.darkBlue) self.keywordFormat.setFontWeight(QtGui.QFont.Bold) self.callableFormat = QtGui.QTextCharFormat(self.base_format) self.callableFormat.setForeground(QtCore.Qt.darkBlue) self.magicFormat = QtGui.QTextCharFormat(self.base_format) self.magicFormat.setForeground(QtGui.QColor(224, 128, 0)) self.qtFormat = QtGui.QTextCharFormat(self.base_format) self.qtFormat.setForeground(QtCore.Qt.blue) self.qtFormat.setFontWeight(QtGui.QFont.Bold) self.selfFormat = QtGui.QTextCharFormat(self.base_format) self.selfFormat.setForeground(QtCore.Qt.red) # self.selfFormat.setFontItalic(True) self.singleLineCommentFormat = QtGui.QTextCharFormat(self.base_format) self.singleLineCommentFormat.setForeground(QtCore.Qt.darkGreen) self.multiLineStringFormat = QtGui.QTextCharFormat(self.base_format) self.multiLineStringFormat.setBackground( QtGui.QBrush(QtGui.QColor(127, 127, 255))) self.quotationFormat1 = QtGui.QTextCharFormat(self.base_format) self.quotationFormat1.setForeground(QtCore.Qt.blue) self.quotationFormat2 = QtGui.QTextCharFormat(self.base_format) self.quotationFormat2.setForeground(QtCore.Qt.blue) def updateRules(self): self.rules = [] self.rules += map(lambda s: (QtCore.QRegExp(r"\b" + s + r"\b"), - self.keywordFormat), self.keywords) + self.keywordFormat), self.keywords) self.rules.append((QtCore.QRegExp(r"\b[A-Za-z_]+\(.*\)"), self.callableFormat)) self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat)) self.rules.append((QtCore.QRegExp(r"\bself\b"), self.selfFormat)) self.rules.append((QtCore.QRegExp(r"\bQ([A-Z][a-z]*)+\b"), self.qtFormat)) self.rules.append((QtCore.QRegExp(r"#[^\n]*"), self.singleLineCommentFormat)) self.multiLineStringBegin = QtCore.QRegExp(r'\"\"\"') self.multiLineStringEnd = QtCore.QRegExp(r'\"\"\"') self.rules.append((QtCore.QRegExp(r'\"[^\n]*\"'), self.quotationFormat1)) self.rules.append((QtCore.QRegExp(r"'[^\n]*'"), self.quotationFormat2)) def updateHighlighter(self, font): self.updateFonts(font) self.updateRules() self.setDocument(self.document) class QtQmlHighlighter(PythonHighlighter): keywords = """" break for throw case function try catch if typeof continue in var default instanceof void delete new undefined do return while else switch with finally this """.split() + \ ['NaN', 'Infinity', 'undefined', 'print', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'escape', 'unescape', 'version', 'gc', 'Object', 'Function', 'Number', 'Boolean', 'String', 'Date', 'Array', 'RegExp', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'eval', 'Math', 'Enumeration', 'Variant', 'QObject', 'QMetaObject'] def __init__(self, edit): PythonHighlighter.__init__(self, edit) def updateRules(self): self.rules = [] self.rules += map(lambda s: (QtCore.QRegExp(r"\b" + s + r"\b"), - self.keywordFormat), self.keywords) + self.keywordFormat), self.keywords) self.rules.append((QtCore.QRegExp(r"\b[A-Za-z_]+\(.*\)"), self.callableFormat)) # self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat)) self.rules.append((QtCore.QRegExp(r"\bthis\b"), self.selfFormat)) self.rules.append((QtCore.QRegExp(r"\bQ([A-Z][a-z]*)+\b"), self.qtFormat)) self.rules.append((QtCore.QRegExp(r"//[^\n]*"), self.singleLineCommentFormat)) # XXX quick hack to support QtQml syntax self.multiLineStringBegin = QtCore.QRegExp(r'/\*') self.multiLineStringEnd = QtCore.QRegExp(r'\*/') self.multiLineStringFormat = self.singleLineCommentFormat self.rules.append((QtCore.QRegExp(r'\"[^\n]*\"'), self.quotationFormat1)) self.rules.append((QtCore.QRegExp(r"'[^\n]*'"), self.quotationFormat2)) diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py index c331fe6941..e73f4c8512 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py @@ -1,415 +1,415 @@ # -*- coding: utf-8 -*- import re import sys import os # I put the rope package into a ZIP-file to save space # and to keep everything clear path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(path, "rope.zip")) from rope.base.project import get_no_project from rope.contrib.codeassist import code_assist from PyQt5.QtCore import QCoreApplication, QLine, Qt from PyQt5.QtGui import (QBrush, QColor, QFont, QKeyEvent, QTextBlockUserData, QTextCursor, QPainter, QPalette, QPen) from PyQt5.QtWidgets import (QApplication, QFrame, QHBoxLayout, QMessageBox, QPlainTextEdit, QVBoxLayout, QWidget) from indenter import PythonCodeIndenter from assist import AutoComplete, CallTip from highlighter import PythonHighlighter, QtQmlHighlighter class EditorBlockData(QTextBlockUserData): def __init__(self): QTextBlockUserData.__init__(self) class RopeEditorWrapper(object): def __init__(self, editview): self.editview = editview def length(self): return self.editview.length() def line_editor(self): return self def _get_block(self, line_no=None): cursor = self.editview.textCursor() row = cursor.blockNumber() if line_no == None: line_no = row block = cursor.block() while row > line_no: block = block.previous() row -= 1 while row < line_no: block = block.next() row += 1 return block def get_line(self, line_no=None): return unicode(self._get_block(line_no).text()) def indent_line(self, line_no, indent_length): block = self._get_block(line_no) cursor = QTextCursor(block) cursor.joinPreviousEditBlock() cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) if indent_length < 0: for i in range(-indent_length): cursor.deleteChar() else: cursor.insertText(" " * indent_length) if indent_length: cursor.movePosition( QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) line = unicode(cursor.block().text()) if len(line) and line[0] == " ": cursor.movePosition( QTextCursor.NextWord, QTextCursor.MoveAnchor) self.editview.setTextCursor(cursor) cursor.endEditBlock() class EditorView(QPlainTextEdit): def __init__(self, parent=None, text=None, EditorHighlighterClass=PythonHighlighter, indenter=PythonCodeIndenter): QPlainTextEdit.__init__(self, parent) self.setFrameStyle(QFrame.NoFrame) self.setTabStopWidth(4) self.setLineWrapMode(QPlainTextEdit.NoWrap) font = QFont() font.setFamily("lucidasanstypewriter") font.setFixedPitch(True) font.setPointSize(10) self.setFont(font) self.highlighter = EditorHighlighterClass(self) if text: self.setPlainText(text) self.frame_style = self.frameStyle() self.draw_line = True self.print_width = self.fontMetrics().width("x" * 78) self.line_pen = QPen(QColor("lightgrey")) self.last_row = self.last_col = -1 self.last_block = None self.highlight_line = True self.highlight_color = self.palette().highlight().color().light(175) self.highlight_brush = QBrush(QColor(self.highlight_color)) self.cursorPositionChanged.connect(self.onCursorPositionChanged) self.indenter = indenter(RopeEditorWrapper(self)) # True if you want to catch Emacs keys in actions self.disable_shortcuts = False self.prj = get_no_project() self.prj.root = None self.calltip = CallTip(self) self.autocomplete = AutoComplete(self) def closeEvent(self, event): self.calltip.close() self.autocomplete.close() def isModified(self): return self.document().isModified() def setModified(self, flag): self.document().setModified(flag) def length(self): return self.document().blockCount() def goto(self, line_no): cursor = self.textCursor() block = cursor.block() row = cursor.blockNumber() while row > line_no: block = block.previous() row -= 1 while row < line_no: block = block.next() row += 1 cursor = QTextCursor(block) self.setTextCursor(cursor) def move_start_of_doc(self): cursor = self.textCursor() cursor.setPosition(0) self.setTextCursor(cursor) def move_end_of_doc(self): cursor = self.textCursor() block = cursor.block() while block.isValid(): last_block = block block = block.next() cursor.setPosition(last_block.position()) cursor.movePosition( QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) def move_start_of_row(self): cursor = self.textCursor() cursor.movePosition( QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) def move_end_of_row(self): cursor = self.textCursor() cursor.movePosition( QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) def highline(self, cursor): self.viewport().update() def onCursorPositionChanged(self): cursor = self.textCursor() row, col = cursor.blockNumber(), cursor.columnNumber() if self.last_row != row: self.last_row = row if self.highlight_line: self.highline(cursor) if col != self.last_col: self.last_col = col self.cursorPositionChanged.emit(row, col) def _create_line(self): x = self.print_width self.line = QLine(x, 0, x, self.height()) def resizeEvent(self, event): self._create_line() QPlainTextEdit.resizeEvent(self, event) def paintEvent(self, event): painter = QPainter(self.viewport()) if self.highlight_line: r = self.cursorRect() r.setX(0) r.setWidth(self.viewport().width()) painter.fillRect(r, self.highlight_brush) if self.draw_line: painter.setPen(self.line_pen) painter.drawLine(self.line) painter.end() QPlainTextEdit.paintEvent(self, event) def setDocument(self, document): QPlainTextEdit.setDocument(self, document) self.highlighter.setDocument(document) def indent(self): self.indenter.correct_indentation(self.textCursor().blockNumber()) def tab_pressed(self): self.indent() def dedent(self): self.indenter.deindent(self.textCursor().blockNumber()) def backtab_pressed(self): self.dedent() return True def backspace_pressed(self): cursor = self.textCursor() text = unicode(cursor.block().text()) col = cursor.columnNumber() if col > 0 and text[:col].strip() == "": self.indenter.deindent(self.textCursor().blockNumber()) return True def autocomplete_pressed(self): try: items = code_assist(self.prj, unicode(self.toPlainText()), self.textCursor().position()) except Exception as e: items = [] if items: self.autocomplete.setItems(items) self.autocomplete.show() def after_return_pressed(self): self.indenter.entering_new_line(self.textCursor().blockNumber()) def keyPressEvent(self, event): if self.autocomplete.active: if self.autocomplete.keyPressEvent(event): return elif self.calltip.active: if self.calltip.keyPressEvent(event): return m = event.modifiers() k = event.key() t = event.text() # Disable some shortcuts if self.disable_shortcuts and \ m & Qt.ControlModifier and k in [Qt.Key_A, Qt.Key_R, Qt.Key_C, Qt.Key_K, Qt.Key_X, Qt.Key_V, Qt.Key_Y, Qt.Key_Z]: new_ev = QKeyEvent(event.type(), k, m, t) event.ignore() QCoreApplication.postEvent(self.parent(), new_ev) return elif k == Qt.Key_Tab: if self.tab_pressed(): return elif k == Qt.Key_Backtab: if self.backtab_pressed(): return elif k == Qt.Key_Backspace: if self.backspace_pressed(): return elif k == Qt.Key_Period or \ (k == Qt.Key_Space and event.modifiers() == Qt.ControlModifier): QPlainTextEdit.keyPressEvent(self, event) self.autocomplete_pressed() return elif k in [Qt.Key_ParenLeft, Qt.Key_BraceLeft, Qt.Key_BracketLeft]: QPlainTextEdit.keyPressEvent(self, event) self.paren_opened(k) return QPlainTextEdit.keyPressEvent(self, event) if k == Qt.Key_Return or k == Qt.Key_Enter: self.after_return_pressed() def paren_opened(self, key): close_char = { Qt.Key_ParenLeft: ")", Qt.Key_BraceLeft: " }", Qt.Key_BracketLeft: "]" } cursor = self.textCursor() cursor.insertText(close_char[key]) cursor.setPosition(cursor.position() - 1) self.setTextCursor(cursor) class EditorSidebar(QWidget): def __init__(self, editor): QWidget.__init__(self, editor) self.editor = editor self.view = editor.view self.doc = editor.view.document self.fm = self.fontMetrics() self.show_line_numbers = True self.setAutoFillBackground(True) # bg = editor.view.palette().base().color() # pal = QPalette() # pal.setColor(self.backgroundRole(), bg) # self.setPalette(pal) self.setBackgroundRole(QPalette.Base) self.doc().documentLayout().update.connect(self.update) self.view.verticalScrollBar().valueChanged.connect(self.update) self.first_row = self.last_row = self.rows = 0 width = 10 if self.show_line_numbers: width += self.fm.width("00000") self.setFixedWidth(width) def paintEvent(self, event): QWidget.paintEvent(self, event) p = QPainter(self) view = self.view first = view.firstVisibleBlock() first_row = first.blockNumber() block = first row = first_row y = view.contentOffset().y() pageBottom = max( view.height(), - view.verticalScrollBar().value() + view.viewport().height()) + view.verticalScrollBar().value() + view.viewport().height()) fm = self.fm w = self.width() - 8 while block.isValid(): txt = str(row).rjust(5) y = view.blockBoundingGeometry(block).y() if y >= pageBottom: break x = w - fm.width(txt) p.drawText(x, y, txt) row += 1 block = block.next() p.end() class EditorWidget(QFrame): def __init__(self, parent=None, text=None, EditorSidebarClass=EditorSidebar, EditorViewClass=EditorView): QFrame.__init__(self, parent) self.view = EditorViewClass(self, text) self.sidebar = EditorSidebarClass(self) self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.setLineWidth(2) self.vlayout = QVBoxLayout() self.vlayout.setSpacing(0) self.setLayout(self.vlayout) self.hlayout = QHBoxLayout() self.vlayout.addLayout(self.hlayout) self.hlayout.addWidget(self.sidebar) self.hlayout.addWidget(self.view) self.vlayout.setContentsMargins(2, 2, 2, 2) def setPlainText(self, text): self.view.document().setPlainText(text) self.view.setModified(False) def isModified(self): return self.view.document().isModified() def toPlainText(self): return unicode(self.view.document().toPlainText()) def setModified(self, flag): self.view.document().setModified(flag) class PythonEditorWidget(EditorWidget): pass class QtQmlEditorWidget(QPlainTextEdit): def __init__(self, parent): QPlainTextEdit.__init__(self, parent) self.highlighter = QtQmlHighlighter(self) class SaveDialog(QMessageBox): def __init__(self, msg): QMessageBox.__init__(self) self.setWindowTitle("Save") self.setText(msg) self.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) self.setDefaultButton(QMessageBox.Save) if __name__ == "__main__": if __file__ == "": __file__ = "./widget.py" import sys app = QApplication(sys.argv) src = open(__file__).read() edit = EditorWidget(text=src) edit.resize(640, 480) edit.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/plugin.cpp b/plugins/extensions/pykrita/plugin/plugin.cpp index 0201a53601..c0b08f7981 100644 --- a/plugins/extensions/pykrita/plugin/plugin.cpp +++ b/plugins/extensions/pykrita/plugin/plugin.cpp @@ -1,93 +1,84 @@ /* * Copyright (c) 2014 Boudewijn Rempt (boud@valdyas.org) * * 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; version 2 of the License. * * 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 "plugin.h" #include "engine.h" #include "utilities.h" #include #include #include #include #include #include "pyqtpluginsettings.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaPyQtPluginFactory, "kritapykrita.json", registerPlugin();) KritaPyQtPlugin::KritaPyQtPlugin(QObject *parent, const QVariantList &) : KisViewPlugin(parent) , m_engineFailureReason(m_engine.tryInitializeGetFailureReason()) , m_autoReload(false) { qDebug() << "Loading Python plugin"; KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PyQtPluginSettingsFactory* settingsFactory = new PyQtPluginSettingsFactory(&m_engine); - QByteArray pythonPath = qgetenv("PYTHONPATH"); - qDebug() << "\tPython path:" << pythonPath; - - QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts"); - qDebug() << "\tPython plugin directories" << pluginDirectories; - Q_FOREACH(const QString pluginDir, pluginDirectories) { - pythonPath.prepend(pluginDir.toUtf8() + ":"); - } - qputenv("PYTHONPATH", pythonPath); //load and save preferences //if something in kritarc is missing, then the default from this load function will be used and saved back to kconfig. //this way, cfg.readEntry() in any part won't be able to set its own default KisPreferenceSet* settings = settingsFactory->createPreferenceSet(); Q_ASSERT(settings); settings->loadPreferences(); settings->savePreferences(); delete settings; preferenceSetRegistry->add("PyQtPluginSettingsFactory", settingsFactory); // Try to import the `pykrita` module PyKrita::Python py = PyKrita::Python(); PyObject* pykritaPackage = py.moduleImport("pykrita"); pykritaPackage = py.moduleImport("krita"); if (pykritaPackage) { dbgScript << "Loaded pykrita, now load plugins"; m_engine.tryLoadEnabledPlugins(); //py.functionCall("_pykritaLoaded", PyKrita::Python::PYKRITA_ENGINE); } else { dbgScript << "Cannot load pykrita module"; m_engine.setBroken(); } Q_FOREACH (Extension* ext, Krita::instance()->extensions()) { ext->setup(); } } KritaPyQtPlugin::~KritaPyQtPlugin() { } #include "plugin.moc" diff --git a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt index 4a58aa22f7..22983b8e2a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt +++ b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt @@ -1,99 +1,105 @@ # Copyright (C) 2012, 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-2016 Boudewijn Rempt # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. include(CMakeParseArguments) # # Simple helper function to install plugin and related files # having only a name of the plugin... # (just to reduce syntactic noise when a lot of plugins get installed) # function(install_pykrita_plugin name) set(_options) set(_one_value_args) set(_multi_value_args PATTERNS FILE) cmake_parse_arguments(install_pykrita_plugin "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN}) if(NOT name) message(FATAL_ERROR "Plugin filename is not given") endif() if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py) install(FILES kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) foreach(_f ${name}.py ${name}.ui ${install_pykrita_plugin_FILE}) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${_f}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${_f} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) endif() endforeach() elseif(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name}) install(FILES ${name}/kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "*.ui" PATTERN "__pycache__*" EXCLUDE ) # TODO Is there any way to form a long PATTERN options string # and use it in a single install() call? # NOTE Install specified patterns one-by-one... foreach(_pattern ${install_pykrita_plugin_PATTERNS}) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "${_pattern}" PATTERN "__pycache__*" EXCLUDE ) endforeach() else() message(FATAL_ERROR "Do not know what to do with ${name}") endif() endfunction() install_pykrita_plugin(hello) install_pykrita_plugin(assignprofiledialog) install_pykrita_plugin(scripter) +install_pykrita_plugin(colorspace) +install_pykrita_plugin(documenttools) +install_pykrita_plugin(filtermanager) +install_pykrita_plugin(exportlayers) #install_pykrita_plugin(highpass) install_pykrita_plugin(tenbrushes) install( FILES tenbrushes/tenbrushes.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) install_pykrita_plugin(palette_docker) install_pykrita_plugin(quick_settings_docker) +install_pykrita_plugin(lastdocumentsdocker) +install_pykrita_plugin(scriptdocker) # if(PYTHON_VERSION_MAJOR VERSION_EQUAL 3) # install_pykrita_plugin(cmake_utils) # install_pykrita_plugin(js_utils PATTERNS "*.json") # install_pykrita_plugin(expand PATTERNS "*.expand" "templates/*.tpl") # endif() install( DIRECTORY libkritapykrita DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "__pycache__*" EXCLUDE ) diff --git a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop index 6c744694b5..178f1d26d0 100644 --- a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop @@ -1,32 +1,34 @@ [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[pt_BR]=Atribuir perfil a 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[pt_BR]=Atribui um perfil para uma imagem sem convertê-la. 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/colorspace/__init__.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/__init__.py new file mode 100644 index 0000000000..027761f693 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/__init__.py @@ -0,0 +1,2 @@ +# let's make a module +from .colorspace import * diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspace.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspace.py new file mode 100644 index 0000000000..fb7cdd7c56 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspace.py @@ -0,0 +1,20 @@ +import krita +from colorspace import uicolorspace + + +class ColorSpaceExtension(krita.Extension): + + def __init__(self, parent): + super(ColorSpaceExtension, self).__init__(parent) + + def setup(self): + action = krita.Krita.instance().createAction("color_space", "Color Space") + action.setToolTip("Plugin to change color space to selected documents") + action.triggered.connect(self.initialize) + + def initialize(self): + self.uicolorspace = uicolorspace.UIColorSpace() + self.uicolorspace.initialize() + + +Scripter.addExtension(ColorSpaceExtension(krita.Krita.instance())) diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspace.qrc b/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspace.qrc new file mode 100644 index 0000000000..e18c8e5f30 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspace.qrc @@ -0,0 +1,5 @@ + + + icons/refresh.svg + + diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspacedialog.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspacedialog.py new file mode 100644 index 0000000000..0213e943d0 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/colorspacedialog.py @@ -0,0 +1,10 @@ +from PyQt5.QtWidgets import QDialog + + +class ColorSpaceDialog(QDialog): + + def __init__(self, parent=None): + super(ColorSpaceDialog, self).__init__(parent) + + def closeEvent(self, event): + event.accept() diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/components/__init__.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/components/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colordepthcombobox.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colordepthcombobox.py new file mode 100644 index 0000000000..11c6733991 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colordepthcombobox.py @@ -0,0 +1,14 @@ +from PyQt5.QtWidgets import QComboBox + + +class ColorDepthComboBox(QComboBox): + + def __init__(self, uiColorSpace, parent=None): + super(ColorDepthComboBox, self).__init__(parent) + + self.uiColorSpace = uiColorSpace + + self.currentTextChanged.connect(self.changedTextColorDepthComboBox) + + def changedTextColorDepthComboBox(self, colorDepth): + self.uiColorSpace.loadColorProfiles() diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colormodelcombobox.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colormodelcombobox.py new file mode 100644 index 0000000000..8cfdeab7a0 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colormodelcombobox.py @@ -0,0 +1,14 @@ +from PyQt5.QtWidgets import QComboBox + + +class ColorModelComboBox(QComboBox): + + def __init__(self, uiColorSpace, parent=None): + super(ColorModelComboBox, self).__init__(parent) + + self.uiColorSpace = uiColorSpace + + self.currentTextChanged.connect(self.changedTextColorModelComboBox) + + def changedTextColorModelComboBox(self, colorModel): + self.uiColorSpace.loadColorDepths() diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colorprofilecombobox.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colorprofilecombobox.py new file mode 100644 index 0000000000..70628e5fdb --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/components/colorprofilecombobox.py @@ -0,0 +1,10 @@ +from PyQt5.QtWidgets import QComboBox + + +class ColorProfileComboBox(QComboBox): + + def __init__(self, uiColorSpace, parent=None): + super(ColorProfileComboBox, self).__init__(parent) + + self.uiColorSpace = uiColorSpace + self.setSizeAdjustPolicy(self.AdjustToContents) diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/icons/refresh.svg b/plugins/extensions/pykrita/plugin/plugins/colorspace/icons/refresh.svg new file mode 100644 index 0000000000..e352bcedcb --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/icons/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/kritapykrita_colorspace.desktop b/plugins/extensions/pykrita/plugin/plugins/colorspace/kritapykrita_colorspace.desktop new file mode 100644 index 0000000000..9805d8dfbc --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/kritapykrita_colorspace.desktop @@ -0,0 +1,27 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=colorspace +X-Python-2-Compatible=false +Name=Color Space +Name[ca]=Espai de color +Name[ca@valencia]=Espai de color +Name[it]=Spazio dei colori +Name[nl]=Kleurruimte +Name[pt]=Espaço de Cores +Name[pt_BR]=Espaço de cores +Name[sk]=Farebný priestor +Name[sv]=Färgrymd +Name[uk]=Простір кольорів +Name[x-test]=xxColor Spacexx +Name[zh_CN]=色彩空间 +Comment=Plugin to change color space to selected documents +Comment[ca]=Un connector per canviar l'espai de color dels documents seleccionats +Comment[ca@valencia]=Un connector per canviar l'espai de color dels documents seleccionats +Comment[it]=Estensione per cambiare lo spazio dei colori ai documenti selezionati +Comment[nl]=Plug-in om kleurruimte in geselecteerde documenten te wijzigen +Comment[pt]='Plugin' para mudar o espaço de cores do documento seleccionado +Comment[pt_BR]=Plug-in para alterar o espaço de cores em documentos selecionados +Comment[sv]=Insticksprogram för att ändra färgrymd för valda dokument +Comment[uk]=Додаток для зміни простору кольорів у позначених документах +Comment[x-test]=xxPlugin to change color space to selected documentsxx diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/resources_rc.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/resources_rc.py new file mode 100644 index 0000000000..9c74527c16 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/resources_rc.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.9.0) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x02\x24\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x3f\x3e\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x73\ +\x76\x67\x20\x20\x50\x55\x42\x4c\x49\x43\x20\x27\x2d\x2f\x2f\x57\ +\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\x2f\ +\x2f\x45\x4e\x27\x20\x20\x27\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x27\x3e\x3c\x73\x76\x67\x20\x65\x6e\ +\x61\x62\x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3d\ +\x22\x6e\x65\x77\x20\x30\x20\x30\x20\x34\x31\x20\x33\x34\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x33\x34\x70\x78\x22\x20\x69\x64\ +\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\x20\x76\x65\x72\x73\x69\ +\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x76\x69\x65\x77\x42\x6f\x78\ +\x3d\x22\x30\x20\x30\x20\x34\x31\x20\x33\x34\x22\x20\x77\x69\x64\ +\x74\x68\x3d\x22\x34\x31\x70\x78\x22\x20\x78\x6d\x6c\x3a\x73\x70\ +\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x20\x78\ +\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\ +\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\ +\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\ +\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\ +\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x3e\x3c\x70\x61\ +\x74\x68\x20\x64\x3d\x22\x4d\x33\x33\x2e\x39\x34\x39\x2c\x31\x36\ +\x43\x33\x33\x2e\x34\x32\x39\x2c\x37\x2e\x30\x38\x2c\x32\x36\x2e\ +\x30\x35\x31\x2c\x30\x2c\x31\x37\x2c\x30\x43\x37\x2e\x36\x31\x31\ +\x2c\x30\x2c\x30\x2c\x37\x2e\x36\x31\x31\x2c\x30\x2c\x31\x37\x73\ +\x37\x2e\x36\x31\x31\x2c\x31\x37\x2c\x31\x37\x2c\x31\x37\x76\x2d\ +\x36\x63\x2d\x36\x2e\x30\x37\x35\x2c\x30\x2d\x31\x31\x2d\x34\x2e\ +\x39\x32\x35\x2d\x31\x31\x2d\x31\x31\x20\x20\x53\x31\x30\x2e\x39\ +\x32\x35\x2c\x36\x2c\x31\x37\x2c\x36\x63\x35\x2e\x37\x33\x37\x2c\ +\x30\x2c\x31\x30\x2e\x34\x34\x33\x2c\x34\x2e\x33\x39\x34\x2c\x31\ +\x30\x2e\x39\x34\x39\x2c\x31\x30\x68\x2d\x36\x2e\x38\x34\x39\x4c\ +\x33\x31\x2c\x32\x35\x2e\x38\x39\x39\x4c\x34\x30\x2e\x38\x39\x39\ +\x2c\x31\x36\x48\x33\x33\x2e\x39\x34\x39\x7a\x22\x20\x66\x69\x6c\ +\x6c\x3d\x22\x23\x32\x33\x31\x46\x32\x30\x22\x2f\x3e\x3c\x2f\x73\ +\x76\x67\x3e\ +" + +qt_resource_name = b"\ +\x00\x05\ +\x00\x6f\xa6\x53\ +\x00\x69\ +\x00\x63\x00\x6f\x00\x6e\x00\x73\ +\x00\x0b\ +\x0c\x6a\x21\xc7\ +\x00\x72\ +\x00\x65\x00\x66\x00\x72\x00\x65\x00\x73\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +" + + +def qInitResources(): + QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/uicolorspace.py b/plugins/extensions/pykrita/plugin/plugins/colorspace/uicolorspace.py new file mode 100644 index 0000000000..1b0942c1ce --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/uicolorspace.py @@ -0,0 +1,118 @@ +from colorspace import colorspacedialog +from colorspace.components import colormodelcombobox, colordepthcombobox, colorprofilecombobox +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QFormLayout, QListWidget, QListWidgetItem, + QAbstractItemView, QComboBox, QDialogButtonBox, + QVBoxLayout, QFrame, QMessageBox, QPushButton, + QHBoxLayout, QAbstractScrollArea) +from PyQt5.QtGui import QIcon +import krita +from colorspace import resources_rc + + +class UIColorSpace(object): + + def __init__(self): + self.mainDialog = colorspacedialog.ColorSpaceDialog() + self.mainLayout = QVBoxLayout(self.mainDialog) + self.formLayout = QFormLayout() + self.documentLayout = QVBoxLayout() + self.refreshButton = QPushButton(QIcon(':/icons/refresh.svg'), "Refresh") + self.widgetDocuments = QListWidget() + self.colorModelComboBox = colormodelcombobox.ColorModelComboBox(self) + self.colorDepthComboBox = colordepthcombobox.ColorDepthComboBox(self) + self.colorProfileComboBox = colorprofilecombobox.ColorProfileComboBox(self) + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + self.kritaInstance = krita.Krita.instance() + self.documentsList = [] + self.colorModelsList = [] + self.colorDepthsList = [] + self.colorProfilesList = [] + + self.refreshButton.clicked.connect(self.refreshButtonClicked) + self.buttonBox.accepted.connect(self.confirmButton) + self.buttonBox.rejected.connect(self.mainDialog.close) + + self.mainDialog.setWindowModality(Qt.NonModal) + self.widgetDocuments.setSelectionMode(QAbstractItemView.MultiSelection) + self.widgetDocuments.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) + + def initialize(self): + self.loadDocuments() + self.loadColorModels() + self.loadColorDepths() + self.loadColorProfiles() + + self.documentLayout.addWidget(self.widgetDocuments) + self.documentLayout.addWidget(self.refreshButton) + + self.formLayout.addRow('Documents', self.documentLayout) + self.formLayout.addRow('Color Model', self.colorModelComboBox) + self.formLayout.addRow('Color Depth', self.colorDepthComboBox) + self.formLayout.addRow('Color Profile', self.colorProfileComboBox) + + self.line = QFrame() + self.line.setFrameShape(QFrame.HLine) + self.line.setFrameShadow(QFrame.Sunken) + + self.mainLayout.addLayout(self.formLayout) + self.mainLayout.addWidget(self.line) + self.mainLayout.addWidget(self.buttonBox) + + self.mainDialog.resize(500, 300) + self.mainDialog.setWindowTitle("Color Space") + self.mainDialog.setSizeGripEnabled(True) + self.mainDialog.show() + self.mainDialog.activateWindow() + + def loadColorModels(self): + self.colorModelsList = sorted(self.kritaInstance.colorModels()) + + self.colorModelComboBox.addItems(self.colorModelsList) + + def loadColorDepths(self): + self.colorDepthComboBox.clear() + + colorModel = self.colorModelComboBox.currentText() + self.colorDepthsList = sorted(self.kritaInstance.colorDepths(colorModel)) + + self.colorDepthComboBox.addItems(self.colorDepthsList) + + def loadColorProfiles(self): + self.colorProfileComboBox.clear() + + colorModel = self.colorModelComboBox.currentText() + colorDepth = self.colorDepthComboBox.currentText() + self.colorProfilesList = sorted(self.kritaInstance.profiles(colorModel, colorDepth)) + + self.colorProfileComboBox.addItems(self.colorProfilesList) + + def loadDocuments(self): + self.widgetDocuments.clear() + + self.documentsList = [document for document in self.kritaInstance.documents() if document.fileName()] + + for document in self.documentsList: + self.widgetDocuments.addItem(document.fileName()) + + def refreshButtonClicked(self): + self.loadDocuments() + + def confirmButton(self): + selectedPaths = [item.text() for item in self.widgetDocuments.selectedItems()] + selectedDocuments = [document for document in self.documentsList for path in selectedPaths if path == document.fileName()] + + self.msgBox = QMessageBox(self.mainDialog) + if selectedDocuments: + self.convertColorSpace(selectedDocuments) + self.msgBox.setText("The selected documents has been converted.") + else: + self.msgBox.setText("Select at least one document.") + self.msgBox.exec_() + + def convertColorSpace(self, documents): + for document in documents: + document.setColorSpace(self.colorModelComboBox.currentText(), + self.colorDepthComboBox.currentText(), + self.colorProfileComboBox.currentText()) diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/__init__.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/__init__.py new file mode 100644 index 0000000000..34deb6c918 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/__init__.py @@ -0,0 +1,2 @@ +# let's make a module +from .documenttools import * diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/documenttools.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/documenttools.py new file mode 100644 index 0000000000..126f298d97 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/documenttools.py @@ -0,0 +1,20 @@ +import krita +from documenttools import uidocumenttools + + +class DocumentToolsExtension(krita.Extension): + + def __init__(self, parent): + super(DocumentToolsExtension, self).__init__(parent) + + def setup(self): + action = krita.Krita.instance().createAction("document_tools", "Document Tools") + action.setToolTip("Plugin to manipulate properties of selected documents") + action.triggered.connect(self.initialize) + + def initialize(self): + self.uidocumenttools = uidocumenttools.UIDocumentTools() + self.uidocumenttools.initialize() + + +Scripter.addExtension(DocumentToolsExtension(krita.Krita.instance())) diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/documenttoolsdialog.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/documenttoolsdialog.py new file mode 100644 index 0000000000..b83cdf5ca3 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/documenttoolsdialog.py @@ -0,0 +1,10 @@ +from PyQt5.QtWidgets import QDialog + + +class DocumentToolsDialog(QDialog): + + def __init__(self, parent=None): + super(DocumentToolsDialog, self).__init__(parent) + + def closeEvent(self, event): + event.accept() diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/kritapykrita_documenttools.desktop b/plugins/extensions/pykrita/plugin/plugins/documenttools/kritapykrita_documenttools.desktop new file mode 100644 index 0000000000..e4aa0c9ea5 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/kritapykrita_documenttools.desktop @@ -0,0 +1,25 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=documenttools +X-Python-2-Compatible=false +Name=Document Tools +Name[ca]=Eines de document +Name[ca@valencia]=Eines de document +Name[it]=Strumenti per i documenti +Name[nl]=Documenthulpmiddelen +Name[pt]=Ferramentas de Documentos +Name[pt_BR]=Ferramentas de documento +Name[sv]=Dokumentverktyg +Name[uk]=Засоби документа +Name[x-test]=xxDocument Toolsxx +Comment=Plugin to manipulate properties of selected documents +Comment[ca]=Un connector per manipular propietats dels documents seleccionats +Comment[ca@valencia]=Un connector per manipular propietats dels documents seleccionats +Comment[it]=Estensione per manipolare le proprietà dei documenti selezionati +Comment[nl]=Plug-in om eigenschappen van geselecteerde documenten te manipuleren +Comment[pt]='Plugin' para manipular as propriedades dos documentos seleccionados +Comment[pt_BR]=Plug-in para manipular as propriedades de documentos selecionados +Comment[sv]=Insticksprogram för att ändra egenskaper för valda dokument +Comment[uk]=Додаток для керування властивостями позначених документів +Comment[x-test]=xxPlugin to manipulate properties of selected documentsxx diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/__init__.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/__init__.py new file mode 100644 index 0000000000..81d224691d --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/__init__.py @@ -0,0 +1,3 @@ +ToolClasses = ['canvassizetool.canvassizetool.CanvasSizeTool', + 'scaletool.scaletool.ScaleTool', + 'rotatetool.rotatetool.RotateTool'] diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/canvassizetool/__init__.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/canvassizetool/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/canvassizetool/canvassizetool.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/canvassizetool/canvassizetool.py new file mode 100644 index 0000000000..1296575ae8 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/canvassizetool/canvassizetool.py @@ -0,0 +1,41 @@ +from PyQt5.QtWidgets import (QWidget, QSpinBox, QHBoxLayout, + QVBoxLayout, QFormLayout) + + +class CanvasSizeTool(QWidget): + + def __init__(self, mainDialog, parent=None): + super(CanvasSizeTool, self).__init__(parent) + + self.setObjectName("Canvas Size") + + self.layout = QFormLayout() + self.offsetLayout = QVBoxLayout() + + self.widthSpinBox = QSpinBox() + self.heightSpinBox = QSpinBox() + self.xOffsetSpinBox = QSpinBox() + self.yOffsetSpinBox = QSpinBox() + + self.setLayout(self.layout) + self.initialize() + + def initialize(self): + self.widthSpinBox.setRange(1, 10000) + self.heightSpinBox.setRange(1, 10000) + self.xOffsetSpinBox.setRange(-10000, 10000) + self.yOffsetSpinBox.setRange(-10000, 10000) + + self.offsetLayout.addWidget(self.xOffsetSpinBox) + self.offsetLayout.addWidget(self.yOffsetSpinBox) + + self.layout.addRow('Width', self.widthSpinBox) + self.layout.addRow('Height', self.heightSpinBox) + self.layout.addRow('Offset', self.offsetLayout) + + def adjust(self, documents): + for document in documents: + document.resizeImage(self.xOffsetSpinBox.value(), + self.yOffsetSpinBox.value(), + self.widthSpinBox.value(), + self.heightSpinBox.value()) diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/rotatetool/__init__.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/rotatetool/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/rotatetool/rotatetool.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/rotatetool/rotatetool.py new file mode 100644 index 0000000000..73b6802a3c --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/rotatetool/rotatetool.py @@ -0,0 +1,28 @@ +from PyQt5.QtWidgets import (QWidget, QSpinBox, QHBoxLayout, + QVBoxLayout, QFormLayout) +import math + + +class RotateTool(QWidget): + + def __init__(self, mainDialog, parent=None): + super(RotateTool, self).__init__(parent) + + self.setObjectName("Rotate") + + self.layout = QFormLayout() + + self.degreesSpinBox = QSpinBox() + + self.setLayout(self.layout) + self.initialize() + + def initialize(self): + self.degreesSpinBox.setRange(-180, 180) + self.degreesSpinBox.setToolTip("Negative degrees will rotate the image to the left") + + self.layout.addRow('Degrees', self.degreesSpinBox) + + def adjust(self, documents): + for document in documents: + document.rotateImage(math.radians(self.degreesSpinBox.value())) diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/scaletool/__init__.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/scaletool/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/scaletool/scaletool.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/scaletool/scaletool.py new file mode 100644 index 0000000000..fbd84166c2 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/scaletool/scaletool.py @@ -0,0 +1,49 @@ +from PyQt5.QtWidgets import (QWidget, QSpinBox, QHBoxLayout, + QVBoxLayout, QFormLayout, QComboBox) + + +class ScaleTool(QWidget): + + def __init__(self, mainDialog, parent=None): + super(ScaleTool, self).__init__(parent) + + self.setObjectName("Scale") + + self.layout = QFormLayout() + self.resolutionLayout = QVBoxLayout() + self.widthSpinBox = QSpinBox() + self.heightSpinBox = QSpinBox() + self.xResSpinBox = QSpinBox() + self.yResSpinBox = QSpinBox() + self.strategyComboBox = QComboBox() + + self.strategyComboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.setLayout(self.layout) + self.initialize() + + def initialize(self): + self.widthSpinBox.setRange(1, 10000) + self.heightSpinBox.setRange(1, 10000) + self.xResSpinBox.setRange(1, 10000) + self.yResSpinBox.setRange(1, 10000) + + strategies = ['Hermite', 'Bicubic', 'Box', + 'Bilinear', 'Bell', 'BSpline', + 'Kanczos3', 'Mitchell'] + self.strategyComboBox.addItems(strategies) + + self.resolutionLayout.addWidget(self.xResSpinBox) + self.resolutionLayout.addWidget(self.yResSpinBox) + + self.layout.addRow('Width', self.widthSpinBox) + self.layout.addRow('Height', self.heightSpinBox) + self.layout.addRow('Resolution', self.resolutionLayout) + self.layout.addRow('Filter', self.strategyComboBox) + + def adjust(self, documents): + for document in documents: + document.scaleImage(self.widthSpinBox.value(), + self.heightSpinBox.value(), + self.xResSpinBox.value(), + self.yResSpinBox.value(), + self.strategyComboBox.currentText()) diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/uidocumenttools.py b/plugins/extensions/pykrita/plugin/plugins/documenttools/uidocumenttools.py new file mode 100644 index 0000000000..bda21ef35a --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/uidocumenttools.py @@ -0,0 +1,95 @@ +from documenttools import documenttoolsdialog +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QFormLayout, QListWidget, QAbstractItemView, + QDialogButtonBox, QVBoxLayout, QFrame, QTabWidget, + QPushButton, QAbstractScrollArea, QMessageBox) +import krita +import importlib + + +class UIDocumentTools(object): + + def __init__(self): + self.mainDialog = documenttoolsdialog.DocumentToolsDialog() + self.mainLayout = QVBoxLayout(self.mainDialog) + self.formLayout = QFormLayout() + self.documentLayout = QVBoxLayout() + self.refreshButton = QPushButton("Refresh") + self.widgetDocuments = QListWidget() + self.tabTools = QTabWidget() + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + self.kritaInstance = krita.Krita.instance() + self.documentsList = [] + + self.refreshButton.clicked.connect(self.refreshButtonClicked) + self.buttonBox.accepted.connect(self.confirmButton) + self.buttonBox.rejected.connect(self.mainDialog.close) + + self.mainDialog.setWindowModality(Qt.NonModal) + self.widgetDocuments.setSelectionMode(QAbstractItemView.MultiSelection) + self.widgetDocuments.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) + + def initialize(self): + self.loadDocuments() + self.loadTools() + + self.documentLayout.addWidget(self.widgetDocuments) + self.documentLayout.addWidget(self.refreshButton) + + self.formLayout.addRow('Documents', self.documentLayout) + self.formLayout.addRow(self.tabTools) + + self.line = QFrame() + self.line.setFrameShape(QFrame.HLine) + self.line.setFrameShadow(QFrame.Sunken) + + self.mainLayout.addLayout(self.formLayout) + self.mainLayout.addWidget(self.line) + self.mainLayout.addWidget(self.buttonBox) + + self.mainDialog.resize(500, 300) + self.mainDialog.setWindowTitle("Document Tools") + self.mainDialog.setSizeGripEnabled(True) + self.mainDialog.show() + self.mainDialog.activateWindow() + + def loadTools(self): + modulePath = 'documenttools.tools' + toolsModule = importlib.import_module(modulePath) + modules = [] + + for classPath in toolsModule.ToolClasses: + _module, _klass = classPath.rsplit('.', maxsplit=1) + modules.append(dict(module='{0}.{1}'.format(modulePath, _module), + klass=_klass)) + + for module in modules: + m = importlib.import_module(module['module']) + toolClass = getattr(m, module['klass']) + obj = toolClass(self.mainDialog) + self.tabTools.addTab(obj, obj.objectName()) + + def loadDocuments(self): + self.widgetDocuments.clear() + + self.documentsList = [document for document in self.kritaInstance.documents() if document.fileName()] + + for document in self.documentsList: + self.widgetDocuments.addItem(document.fileName()) + + def refreshButtonClicked(self): + self.loadDocuments() + + def confirmButton(self): + selectedPaths = [item.text() for item in self.widgetDocuments.selectedItems()] + selectedDocuments = [document for document in self.documentsList for path in selectedPaths if path == document.fileName()] + + self.msgBox = QMessageBox(self.mainDialog) + if selectedDocuments: + widget = self.tabTools.currentWidget() + widget.adjust(selectedDocuments) + self.msgBox.setText("The selected documents has been modified.") + else: + self.msgBox.setText("Select at least one document.") + self.msgBox.exec_() diff --git a/plugins/extensions/pykrita/plugin/plugins/exportlayers/__init__.py b/plugins/extensions/pykrita/plugin/plugins/exportlayers/__init__.py new file mode 100644 index 0000000000..b0a4cf13f3 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/exportlayers/__init__.py @@ -0,0 +1,2 @@ +# let's make a module +from .exportlayers import * diff --git a/plugins/extensions/pykrita/plugin/plugins/exportlayers/exportlayers.py b/plugins/extensions/pykrita/plugin/plugins/exportlayers/exportlayers.py new file mode 100644 index 0000000000..1fdc00e7fd --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/exportlayers/exportlayers.py @@ -0,0 +1,20 @@ +import krita +from exportlayers import uiexportlayers + + +class ExportLayersExtension(krita.Extension): + + def __init__(self, parent): + super(ExportLayersExtension, self).__init__(parent) + + def setup(self): + action = krita.Krita.instance().createAction("export_layers", "Export Layers") + action.setToolTip("Plugin to export layers from a document") + action.triggered.connect(self.initialize) + + def initialize(self): + self.uiexportlayers = uiexportlayers.UIExportLayers() + self.uiexportlayers.initialize() + + +Scripter.addExtension(ExportLayersExtension(krita.Krita.instance())) diff --git a/plugins/extensions/pykrita/plugin/plugins/exportlayers/exportlayersdialog.py b/plugins/extensions/pykrita/plugin/plugins/exportlayers/exportlayersdialog.py new file mode 100644 index 0000000000..780c9af762 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/exportlayers/exportlayersdialog.py @@ -0,0 +1,10 @@ +from PyQt5.QtWidgets import QDialog + + +class ExportLayersDialog(QDialog): + + def __init__(self, parent=None): + super(ExportLayersDialog, self).__init__(parent) + + def closeEvent(self, event): + event.accept() diff --git a/plugins/extensions/pykrita/plugin/plugins/exportlayers/kritapykrita_exportlayers.desktop b/plugins/extensions/pykrita/plugin/plugins/exportlayers/kritapykrita_exportlayers.desktop new file mode 100644 index 0000000000..d6b96655ce --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/exportlayers/kritapykrita_exportlayers.desktop @@ -0,0 +1,25 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=exportlayers +X-Python-2-Compatible=false +Name=Export Layers +Name[ca]=Exportació de capes +Name[ca@valencia]=Exportació de capes +Name[it]=Esporta livelli +Name[nl]=Lagen exporteren +Name[pt]=Exportar as Camadas +Name[pt_BR]=Exportar camadas +Name[sv]=Exportera lager +Name[uk]=Експортувати шари +Name[x-test]=xxExport Layersxx +Comment=Plugin to export layers from a document +Comment[ca]=Un connector per exportar capes d'un document +Comment[ca@valencia]=Un connector per exportar capes d'un document +Comment[it]=Estensione per esportare i livelli da un documento +Comment[nl]=Plug-in om lagen uit een document te exporteren +Comment[pt]='Plugin' para exportar as camadas de um documento +Comment[pt_BR]=Plug-in para exportar as camadas de um documento +Comment[sv]=Insticksprogram för att exportera lager från ett dokument +Comment[uk]=Додаток для експортування шарів з документа +Comment[x-test]=xxPlugin to export layers from a documentxx diff --git a/plugins/extensions/pykrita/plugin/plugins/exportlayers/uiexportlayers.py b/plugins/extensions/pykrita/plugin/plugins/exportlayers/uiexportlayers.py new file mode 100644 index 0000000000..d9361423db --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/exportlayers/uiexportlayers.py @@ -0,0 +1,171 @@ +from exportlayers import exportlayersdialog +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QFormLayout, QListWidget, QHBoxLayout, + QDialogButtonBox, QVBoxLayout, QFrame, + QPushButton, QAbstractScrollArea, QLineEdit, + QMessageBox, QFileDialog, QCheckBox, QSpinBox, + QComboBox) +import os +import errno +import krita + + +class UIExportLayers(object): + + def __init__(self): + self.mainDialog = exportlayersdialog.ExportLayersDialog() + self.mainLayout = QVBoxLayout(self.mainDialog) + self.formLayout = QFormLayout() + self.documentLayout = QVBoxLayout() + self.directorySelectorLayout = QHBoxLayout() + self.optionsLayout = QVBoxLayout() + self.resolutionLayout = QHBoxLayout() + + self.refreshButton = QPushButton("Refresh") + self.widgetDocuments = QListWidget() + self.directoryTextField = QLineEdit() + self.directoryDialogButton = QPushButton("...") + self.verifyLayerFormatCheckBox = QCheckBox("Verify layers that contains [jpeg|png]") + self.exportFilterLayersCheckBox = QCheckBox("Export filter layers") + self.batchmodeCheckBox = QCheckBox("Export in batchmode") + self.ignoreInvisibleLayersCheckBox = QCheckBox("Ignore invisible layers") + self.xResSpinBox = QSpinBox() + self.yResSpinBox = QSpinBox() + self.formatsComboBox = QComboBox() + + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + self.kritaInstance = krita.Krita.instance() + self.documentsList = [] + + self.directoryTextField.setReadOnly(True) + self.batchmodeCheckBox.setChecked(True) + self.directoryDialogButton.clicked.connect(self._selectDir) + self.widgetDocuments.currentRowChanged.connect(self._setResolution) + self.refreshButton.clicked.connect(self.refreshButtonClicked) + self.buttonBox.accepted.connect(self.confirmButton) + self.buttonBox.rejected.connect(self.mainDialog.close) + + self.mainDialog.setWindowModality(Qt.NonModal) + self.widgetDocuments.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) + + def initialize(self): + self.loadDocuments() + + self.xResSpinBox.setRange(1, 10000) + self.yResSpinBox.setRange(1, 10000) + + self.formatsComboBox.addItem("jpeg") + self.formatsComboBox.addItem("png") + + self.documentLayout.addWidget(self.widgetDocuments) + self.documentLayout.addWidget(self.refreshButton) + + self.directorySelectorLayout.addWidget(self.directoryTextField) + self.directorySelectorLayout.addWidget(self.directoryDialogButton) + + self.optionsLayout.addWidget(self.verifyLayerFormatCheckBox) + self.optionsLayout.addWidget(self.exportFilterLayersCheckBox) + self.optionsLayout.addWidget(self.batchmodeCheckBox) + self.optionsLayout.addWidget(self.ignoreInvisibleLayersCheckBox) + + self.resolutionLayout.addWidget(self.xResSpinBox) + self.resolutionLayout.addWidget(self.yResSpinBox) + + self.formLayout.addRow('Documents', self.documentLayout) + self.formLayout.addRow('Initial directory', self.directorySelectorLayout) + self.formLayout.addRow('Export options', self.optionsLayout) + self.formLayout.addRow('Resolution', self.resolutionLayout) + self.formLayout.addRow('Images Extensions', self.formatsComboBox) + + self.line = QFrame() + self.line.setFrameShape(QFrame.HLine) + self.line.setFrameShadow(QFrame.Sunken) + + self.mainLayout.addLayout(self.formLayout) + self.mainLayout.addWidget(self.line) + self.mainLayout.addWidget(self.buttonBox) + + self.mainDialog.resize(500, 300) + self.mainDialog.setWindowTitle("Export Layers") + self.mainDialog.setSizeGripEnabled(True) + self.mainDialog.show() + self.mainDialog.activateWindow() + + def loadDocuments(self): + self.widgetDocuments.clear() + + self.documentsList = [document for document in self.kritaInstance.documents() if document.fileName()] + + for document in self.documentsList: + self.widgetDocuments.addItem(document.fileName()) + + def refreshButtonClicked(self): + self.loadDocuments() + + def confirmButton(self): + selectedPaths = [item.text() for item in self.widgetDocuments.selectedItems()] + selectedDocuments = [document for document in self.documentsList for path in selectedPaths if path == document.fileName()] + + self.msgBox = QMessageBox(self.mainDialog) + if not selectedDocuments: + self.msgBox.setText("Select one document.") + elif not self.directoryTextField.text(): + self.msgBox.setText("Select the initial directory.") + else: + self.export(selectedDocuments[0]) + self.msgBox.setText("All layers has been exported.") + self.msgBox.exec_() + + def mkdir(self, directory): + try: + os.makedirs(self.directoryTextField.text() + directory) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + def export(self, document): + Application.setBatchmode(self.batchmodeCheckBox.isChecked()) + + documentName = document.fileName() if document.fileName() else 'Untitled' + fileName, extension = str(documentName).rsplit('/', maxsplit=1)[-1].split('.', maxsplit=1) + self.mkdir('/' + fileName) + + self._exportLayers(document.rootNode(), self.formatsComboBox.currentText(), '/' + fileName) + Application.setBatchmode(True) + + def _exportLayers(self, parentNode, fileFormat, parentDir): + """ This method get all sub-nodes from the current node and export then in + the defined format.""" + + for node in parentNode.childNodes(): + newDir = '' + if node.type() == 'grouplayer': + newDir = parentDir + '/' + node.name() + self.mkdir(newDir) + elif not self.exportFilterLayersCheckBox.isChecked() and 'filter' in node.type(): + continue + elif self.ignoreInvisibleLayersCheckBox.isChecked() and not node.visible(): + continue + else: + nodeName = node.name() + _fileFormat = self.formatsComboBox.currentText() + if '[jpeg]' in nodeName: + _fileFormat = 'jpeg' + elif '[png]' in nodeName: + _fileFormat = 'png' + + layerFileName = '{0}{1}/{2}.{3}'.format(self.directoryTextField.text(), parentDir, node.name(), _fileFormat) + teste = node.save(layerFileName, self.xResSpinBox.value(), self.yResSpinBox.value()) + + if node.childNodes(): + self._exportLayers(node, fileFormat, newDir) + + def _selectDir(self): + directory = QFileDialog.getExistingDirectory(self.mainDialog, "Select a folder", os.path.expanduser("~"), QFileDialog.ShowDirsOnly) + self.directoryTextField.setText(directory) + + def _setResolution(self, index): + document = self.documentsList[index] + self.xResSpinBox.setValue(document.width()) + self.yResSpinBox.setValue(document.height()) diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/__init__.py b/plugins/extensions/pykrita/plugin/plugins/filtermanager/__init__.py new file mode 100644 index 0000000000..3dcf5557d9 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/__init__.py @@ -0,0 +1,2 @@ +# let's make a module +from .filtermanager import * diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/__init__.py b/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtercombobox.py b/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtercombobox.py new file mode 100644 index 0000000000..1ee72c0d5d --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtercombobox.py @@ -0,0 +1,12 @@ +from PyQt5.QtWidgets import QComboBox +import krita + + +class FilterComboBox(QComboBox): + + def __init__(self, uiFilterManager, parent=None): + super(FilterComboBox, self).__init__(parent) + + self.uiFilterManager = uiFilterManager + + self.addItems(self.uiFilterManager.filters) diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtermanagertreeitem.py b/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtermanagertreeitem.py new file mode 100644 index 0000000000..2c40df4d69 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtermanagertreeitem.py @@ -0,0 +1,35 @@ +class FilterManagerTreeItem(object): + + def __init__(self, data, parent=None): + self.itemData = data + self.parentItem = parent + self.childItems = [] + + def appendChild(self, child): + self.childItems.append(child) + + def appenChildren(self, children): + self.childItems.extend(children) + + def child(self, row): + return self.childItems[row] + + def childCount(self): + return len(self.childItems) + + def columnCount(self): + return len(self.itemData) + + def data(self, column): + try: + return self.itemData[column] + except IndexError: + return None + + def row(self): + if self.parentItem: + return self.parentItem.childItems.index(self) + return 0 + + def parent(self): + return self.parentItem diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtermanagertreemodel.py b/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtermanagertreemodel.py new file mode 100644 index 0000000000..653b2943f8 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtermanagertreemodel.py @@ -0,0 +1,123 @@ +from PyQt5.QtCore import QAbstractItemModel, QFile, QIODevice, QModelIndex, Qt +from PyQt5.QtWidgets import QApplication, QTreeView +from filtermanager.components import filtermanagertreeitem +from PyQt5.QtGui import QPixmap + + +class FilterManagerTreeModel(QAbstractItemModel): + + TYPE_COLUMN = 1 + NODE_COLUMN = 3 + DOCUMENT_COLUMN = 4 + + def __init__(self, uiFilterManager, parent=None): + super(FilterManagerTreeModel, self).__init__(parent) + + self.rootItem = filtermanagertreeitem.FilterManagerTreeItem(("Name", "Type", "Thumbnail")) + self.uiFilterManager = uiFilterManager + self._loadTreeModel(self.rootItem) + + def index(self, row, column, parent): + if not self.hasIndex(row, column, parent): + return QModelIndex() + + if parent.isValid(): + parentItem = parent.internalPointer() + else: + parentItem = self.rootItem + + # It's a FilterManagerTreeItem + childItem = parentItem.child(row) + if childItem: + return self.createIndex(row, column, childItem) + else: + return QModelIndex() + + def parent(self, index): + if not index.isValid(): + return QModelIndex() + + childItem = index.internalPointer() + parentItem = childItem.parent() + + if parentItem == self.rootItem: + return QModelIndex() + + return self.createIndex(parentItem.row(), 0, parentItem) + + def rowCount(self, parent): + if parent.column() > 0: + return 0 + + if not parent.isValid(): + parentItem = self.rootItem + else: + parentItem = parent.internalPointer() + return parentItem.childCount() + + def columnCount(self, parent): + if parent.isValid(): + return parent.internalPointer().columnCount() + else: + return self.rootItem.columnCount() + + def data(self, index, role): + if not index.isValid(): + return None + + item = index.internalPointer() + + if role == Qt.UserRole + 1: + return item.data(self.NODE_COLUMN) + + if role == Qt.UserRole + 2: + return item.data(self.DOCUMENT_COLUMN) + + if role == Qt.UserRole + 3: + return item.data(self.TYPE_COLUMN) + + if role != Qt.DisplayRole and role != Qt.DecorationRole: + return None + + return item.data(index.column()) + + def flags(self, index): + if not index.isValid(): + return Qt.NoItemFlags + + return Qt.ItemIsEnabled | Qt.ItemIsSelectable + + def headerData(self, section, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.rootItem.data(section) + + return None + + def _loadTreeModel(self, parent): + for index, document in enumerate(self.uiFilterManager.documents): + rootNode = document.rootNode() + columnData = (document.fileName(), + "Document", + QPixmap.fromImage(document.thumbnail(30, 30)), + rootNode, index) + item = filtermanagertreeitem.FilterManagerTreeItem(columnData, parent) + parent.appendChild(item) + + childNodes = rootNode.childNodes() + if len(childNodes): + self._addSubNodes(childNodes[::-1], item, index) + + def _addSubNodes(self, nodes, parent, documentIndex): + for node in nodes: + nodeName = node.name() + nodeType = node.type() + columnData = ("Unnamed" if nodeName == '' else nodeName, + "Untyped" if nodeType == '' else nodeType, + QPixmap.fromImage(node.thumbnail(30, 30)), + node, documentIndex) + item = filtermanagertreeitem.FilterManagerTreeItem(columnData, parent) + parent.appendChild(item) + + childNodes = node.childNodes() + if len(childNodes): + self._addSubNodes(childNodes[::-1], item, documentIndex) diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/filtermanager.py b/plugins/extensions/pykrita/plugin/plugins/filtermanager/filtermanager.py new file mode 100644 index 0000000000..6356f4577f --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/filtermanager.py @@ -0,0 +1,20 @@ +import krita +from filtermanager import uifiltermanager + + +class FilterManagerExtension(krita.Extension): + + def __init__(self, parent): + super(FilterManagerExtension, self).__init__(parent) + + def setup(self): + action = krita.Krita.instance().createAction("filter_manager", "Filter Manager") + action.setToolTip("Plugin to filters management") + action.triggered.connect(self.initialize) + + def initialize(self): + self.uifiltermanager = uifiltermanager.UIFilterManager() + self.uifiltermanager.initialize() + + +Scripter.addExtension(FilterManagerExtension(krita.Krita.instance())) diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/filtermanagerdialog.py b/plugins/extensions/pykrita/plugin/plugins/filtermanager/filtermanagerdialog.py new file mode 100644 index 0000000000..801bf5b853 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/filtermanagerdialog.py @@ -0,0 +1,10 @@ +from PyQt5.QtWidgets import QDialog + + +class FilterManagerDialog(QDialog): + + def __init__(self, parent=None): + super(FilterManagerDialog, self).__init__(parent) + + def closeEvent(self, event): + event.accept() diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/kritapykrita_filtermanager.desktop b/plugins/extensions/pykrita/plugin/plugins/filtermanager/kritapykrita_filtermanager.desktop new file mode 100644 index 0000000000..1483752bb0 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/kritapykrita_filtermanager.desktop @@ -0,0 +1,25 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=filtermanager +X-Python-2-Compatible=false +Name=Filter Manager +Name[ca]=Gestor de filtres +Name[ca@valencia]=Gestor de filtres +Name[it]=Gestore dei filtri +Name[nl]=Beheerder van filters +Name[pt]=Gestor de Filtros +Name[pt_BR]=Gerenciador de filtros +Name[sv]=Filterhantering +Name[uk]=Керування фільтрами +Name[x-test]=xxFilter Managerxx +Comment=Plugin to filters management +Comment[ca]=Un connector per gestionar filtres +Comment[ca@valencia]=Un connector per gestionar filtres +Comment[it]=Estensione per la gestione dei filtri +Comment[nl]=Plug-in voor beheer van filters +Comment[pt]='Plugin' para a gestão de filtros +Comment[pt_BR]=Plug-in para gerenciamento de filtros +Comment[sv]=Insticksprogram för filterhantering +Comment[uk]=Додаток для керування фільтрами +Comment[x-test]=xxPlugin to filters managementxx diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/uifiltermanager.py b/plugins/extensions/pykrita/plugin/plugins/filtermanager/uifiltermanager.py new file mode 100644 index 0000000000..02a1b881bf --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/uifiltermanager.py @@ -0,0 +1,96 @@ +from filtermanager import filtermanagerdialog +from filtermanager.components import (filtercombobox, filtermanagertreemodel) +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QFormLayout, QAbstractItemView, QDialogButtonBox, + QVBoxLayout, QFrame, QAbstractScrollArea, QWidget, + QTreeView) +import krita + + +class UIFilterManager(object): + + def __init__(self): + self.mainDialog = filtermanagerdialog.FilterManagerDialog() + self.mainLayout = QVBoxLayout(self.mainDialog) + self.formLayout = QFormLayout() + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + self.kritaInstance = krita.Krita.instance() + self._filters = sorted(self.kritaInstance.filters()) + self._documents = self.kritaInstance.documents() + self.treeModel = filtermanagertreemodel.FilterManagerTreeModel(self) + + self.documentsTreeView = QTreeView() + self.filterComboBox = filtercombobox.FilterComboBox(self) + + self.buttonBox.accepted.connect(self.confirmButton) + self.buttonBox.rejected.connect(self.mainDialog.close) + + self.documentsTreeView.setSelectionMode(QAbstractItemView.SingleSelection) + self.mainDialog.setWindowModality(Qt.NonModal) + + def initialize(self): + self.documentsTreeView.setModel(self.treeModel) + self.documentsTreeView.setWindowTitle("Document Tree Model") + self.documentsTreeView.resizeColumnToContents(0) + self.documentsTreeView.resizeColumnToContents(1) + self.documentsTreeView.resizeColumnToContents(2) + + self.formLayout.addRow("Filters", self.filterComboBox) + + self.line = QFrame() + self.line.setFrameShape(QFrame.HLine) + self.line.setFrameShadow(QFrame.Sunken) + + self.mainLayout.addWidget(self.documentsTreeView) + self.mainLayout.addLayout(self.formLayout) + self.mainLayout.addWidget(self.line) + self.mainLayout.addWidget(self.buttonBox) + + self.mainDialog.resize(500, 300) + self.mainDialog.setWindowTitle("Filter Manager") + self.mainDialog.setSizeGripEnabled(True) + self.mainDialog.show() + self.mainDialog.activateWindow() + + def confirmButton(self): + documentsIndexes = [] + + selectionModel = self.documentsTreeView.selectionModel() + for index in selectionModel.selectedRows(): + node = self.treeModel.data(index, Qt.UserRole + 1) + documentIndex = self.treeModel.data(index, Qt.UserRole + 2) + _type = self.treeModel.data(index, Qt.UserRole + 3) + + if _type == 'Document': + self.applyFilterOverDocument(self.documents[documentIndex]) + else: + self.applyFilterOverNode(node, self.documents[documentIndex]) + + documentsIndexes.append(documentIndex) + + self.refreshDocumentsProjections(set(documentsIndexes)) + + def refreshDocumentsProjections(self, indexes): + for index in indexes: + document = self.documents[index] + document.refreshProjection() + + def applyFilterOverNode(self, node, document): + _filter = self.kritaInstance.filter(self.filterComboBox.currentText()) + _filter.apply(node, 0, 0, document.width(), document.height()) + + def applyFilterOverDocument(self, document): + """ This method applies the selected filter just to topLevelNodes, then + if topLevelNodes are GroupLayers, that filter will not be applied. """ + + for node in document.topLevelNodes(): + self.applyFilterOverNode(node, document) + + @property + def filters(self): + return self._filters + + @property + def documents(self): + return self._documents diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py index d9eb4835d2..3c4ceb9998 100644 --- a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py +++ b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py @@ -1,35 +1,74 @@ -import sys -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from krita import * +""" +This is a simple example of a Python script for Krita. +It demonstrates how to set up a custom extension and a custom docker! +""" + +from PyQt5.QtCore import qDebug +from PyQt5.QtWidgets import QWidget, QLabel, QMessageBox +from krita import Krita, Extension, DockWidget, DockWidgetFactory, DockWidgetFactoryBase def hello(): - QMessageBox.information(QWidget(), "Test", "Hello! This is Krita " + Application.version()) + """ + Show a test message box. + """ + QMessageBox.information(QWidget(), "Test", "Hello! This is Krita version %s" % Application.version()) class HelloExtension(Extension): + """ + HelloExtension is a small example extension demonstrating basic Python scripting support in Krita! + """ def __init__(self, parent): + """ + Standard Krita Python extension constructor. + Most of the initialization happens in :func:`setup` + + :param parent: Parent widget + :type parent: :class:`QWidget` or None + """ super().__init__(parent) def setup(self): + """ + This is where most of the setup takes place! + """ qDebug("Hello Setup") action = Krita.instance().createAction("hello_python", "hello") action.triggered.connect(hello) + +# Initialize and add the extension Scripter.addExtension(HelloExtension(Krita.instance())) class HelloDocker(DockWidget): + """ + The HelloDocker is an example of a simple Python-based docker. + """ def __init__(self): + """ + Constructs an instance of HelloDocker and the widget it contains + """ super().__init__() + + # The window title is also used in the Docker menu, + # so it should be set to something sensible! + self.setWindowTitle("HelloDocker") label = QLabel("Hello", self) self.setWidget(label) - self.label = label + self._label = label def canvasChanged(self, canvas): - self.label.setText("Hellodocker: canvas changed") + """ + Override canvasChanged from :class:`DockWidget`. + This gets called when the canvas changes. + You can also access the active canvas via :func:`DockWidget.canvas` + """ + self._label.setText("HelloDocker: canvas changed") + +# Register the docker so Krita can use it! Application.addDockWidgetFactory(DockWidgetFactory("hello", DockWidgetFactoryBase.DockRight, HelloDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop index 3eace05f04..55f9a9ad44 100644 --- a/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop @@ -1,34 +1,38 @@ [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[gl]=Ola mundo Name[it]=Ciao mondo Name[nl]=Hallo wereld Name[pl]=Witaj świecie Name[pt]=Olá Mundo +Name[pt_BR]=Olá mundo +Name[sk]=Ahoj svet 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[pt_BR]=Plug-in 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 f796953a51..bf0f44a779 100644 --- a/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop @@ -1,34 +1,36 @@ [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[pt_BR]=Filtro passa alta 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[pt_BR]=Filtro passa alta, 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/lastdocumentsdocker/__init__.py b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/__init__.py new file mode 100644 index 0000000000..ab33e60529 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/__init__.py @@ -0,0 +1,2 @@ +# let's make a module +from .lastdocumentsdocker import * diff --git a/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop new file mode 100644 index 0000000000..a8a09f7346 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop @@ -0,0 +1,23 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=lastdocumentsdocker +X-Python-2-Compatible=false +Name=Last Documents Docker +Name[ca]=Acoblador dels darrers documents +Name[ca@valencia]=Acoblador dels darrers documents +Name[it]=Area di aggancio Ultimi documenti +Name[nl]=Vastzetter van laatste documenten +Name[pt]=Área dos Últimos Documentos +Name[sv]=Dockningsfönster för senaste dokument +Name[uk]=Бічна панель останніх документів +Name[x-test]=xxLast Documents Dockerxx +Comment=A Python-based docker for show thumbnails to last ten documents +Comment[ca]=Un acoblador basant en el Python per mostrar miniatures dels darrers deu documents +Comment[ca@valencia]=Un acoblador basant en el Python per mostrar miniatures dels darrers deu documents +Comment[it]=Un'area di aggancio basata su Python per mostrare miniature degli ultimi dieci documenti. +Comment[nl]=Een op Python gebaseerde vastzetter om miniaturen te tonen naar de laatste tien documenten. +Comment[pt]=Uma área acoplável, feita em Python, para mostrar as miniaturas dos últimos dez documentos +Comment[sv]=Ett Python-baserat dockningsfönster för att visa miniatyrbilder för de tio senaste dokumenten +Comment[uk]=Бічна панель на основі Python для показу мініатюр останніх десяти документів +Comment[x-test]=xxA Python-based docker for show thumbnails to last ten documentsxx diff --git a/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentsdocker.py b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentsdocker.py new file mode 100644 index 0000000000..ad8673ce4e --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentsdocker.py @@ -0,0 +1,29 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QListView +import krita +from lastdocumentsdocker import lastdocumentslistmodel + + +class LastDocumentsDocker(krita.DockWidget): + + def __init__(self): + super(LastDocumentsDocker, self).__init__() + + self.baseWidget = QWidget() + self.layout = QVBoxLayout() + self.listView = QListView() + + self.listView.setFlow(QListView.LeftToRight) + + self.layout.addWidget(self.listView) + + self.baseWidget.setLayout(self.layout) + self.setWidget(self.baseWidget) + + self.listView.setModel(lastdocumentslistmodel.LastDocumentsListModel()) + self.setWindowTitle("Last Documents Docker") + + def canvasChanged(self, canvas): + pass + + +Application.addDockWidgetFactory(krita.DockWidgetFactory("lastdocumentsdocker", krita.DockWidgetFactoryBase.DockRight, LastDocumentsDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentslistmodel.py b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentslistmodel.py new file mode 100644 index 0000000000..6c49f34992 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentslistmodel.py @@ -0,0 +1,44 @@ +from PyQt5.QtCore import QAbstractListModel, Qt +import krita + + +class LastDocumentsListModel(QAbstractListModel): + + def __init__(self, parent=None): + super(LastDocumentsListModel, self).__init__(parent) + + self.rootItem = ('Path',) + self.kritaInstance = krita.Krita.instance() + self.recentDocuments = [] + + self._loadRecentDocuments() + + def data(self, index, role): + if not index.isValid(): + return None + + if index.row() >= len(self.recentDocuments): + return None + + if role == Qt.DecorationRole: + return self.recentDocuments[index.row()] + else: + return None + + def rowCount(self, parent): + return len(self.recentDocuments) + + def headerData(self, section, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.rootItem[section] + + return None + + def _loadRecentDocuments(self): + recentDocumentsPaths = self.kritaInstance.recentDocuments() + + for path in recentDocumentsPaths: + document = self.kritaInstance.openDocument(path) + if document: + self.recentDocuments.append(document.thumbnail(70, 60)) + document.close() diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/__init__.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/__init__.py index a73628950f..f15a5c7a32 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/__init__.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/__init__.py @@ -1,2 +1,2 @@ - # let's make a module +# let's make a module from .palette_docker import * diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/kritapykrita_palette_docker.desktop b/plugins/extensions/pykrita/plugin/plugins/palette_docker/kritapykrita_palette_docker.desktop index 31ad26b5b6..9baabcee4e 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/kritapykrita_palette_docker.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/kritapykrita_palette_docker.desktop @@ -1,25 +1,26 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=palette_docker X-Python-2-Compatible=false Name=Palette docker Name[ca]=Acoblador de paletes Name[ca@valencia]=Acoblador de paletes Name[es]=Panel de paleta +Name[gl]=Doca de paleta Name[it]=Area di aggancio della tavolozza Name[nl]=Vastzetter van palet Name[pt]=Área acoplável da paleta Name[sv]=Dockningsfönster för palett Name[uk]=Панель палітри Name[x-test]=xxPalette dockerxx Comment=A Python-based docker to edit color palettes. Comment[ca]=Un acoblador basant en el Python per editar paletes de colors. Comment[ca@valencia]=Un acoblador basant en el Python per editar paletes de colors. Comment[es]=Un panel basado en Python para editar paletas de colores. Comment[it]=Un'area di aggancio per modificare le tavolozze di colori basata su Python. Comment[nl]=Een op Python gebaseerde vastzetter om kleurpaletten te bewerken. Comment[pt]=Uma área acoplável, feita em Python, para editar paletas de cores. Comment[sv]=Ett Python-baserat dockningsfönster för att redigera färgpaletter. Comment[uk]=Бічна панель для редагування палітр кольорів на основі Python. Comment[x-test]=xxA Python-based docker to edit color palettes.xx diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py index 8281116603..4e9ecb0efd 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py @@ -1,216 +1,232 @@ # Description: A Python based docker that allows you to edit KPL color palettes. # By Wolthera # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.Qt import * import math from krita import * # import the exporters -from . import palette_exporter_gimppalette, palette_exporter_inkscapeSVG +from . import palette_exporter_gimppalette, palette_exporter_inkscapeSVG, palette_sortColors class Palette_Docker(DockWidget): -# Init the docker + # Init the docker def __init__(self): super().__init__() # make base-widget and layout widget = QWidget() layout = QVBoxLayout() buttonLayout = QHBoxLayout() widget.setLayout(layout) self.setWindowTitle("Python Palette Docker") # Make a combobox and add palettes self.cmb_palettes = QComboBox() allPalettes = Application.resources("palette") for palette_name in allPalettes: self.cmb_palettes.addItem(palette_name) + self.cmb_palettes.model().sort(0) self.currentPalette = Palette(allPalettes[list(allPalettes.keys())[0]]) self.cmb_palettes.currentTextChanged.connect(self.slot_paletteChanged) layout.addWidget(self.cmb_palettes) # add combobox to the layout self.paletteView = PaletteView() self.paletteView.setPalette(self.currentPalette) layout.addWidget(self.paletteView) self.paletteView.entrySelectedForeGround.connect(self.slot_swatchSelected) self.colorComboBox = QComboBox() self.colorList = list() buttonLayout.addWidget(self.colorComboBox) - self.addEntry = QPushButton() - self.addEntry.setText("A") - self.addEntry.setToolTip("Add Entry") - self.addEntry.clicked.connect(self.slot_add_entry) - buttonLayout.addWidget(self.addEntry) - self.addGroup = QPushButton() - self.addGroup.clicked.connect(self.slot_add_group) - self.addGroup.setText("G") - self.addGroup.setToolTip("Add Group") - buttonLayout.addWidget(self.addGroup) - self.removeEntry = QPushButton() - self.removeEntry.setText("R") - self.removeEntry.setToolTip("Remove Entry") - self.removeEntry.clicked.connect(self.slot_remove_entry) - buttonLayout.addWidget(self.removeEntry) + self.addEntry = QAction() + self.addEntry.setText("Add Entry") + self.addEntry.setIconText("+") + self.addEntry.triggered.connect(self.slot_add_entry) + self.addGroup = QAction() + self.addGroup.triggered.connect(self.slot_add_group) + self.addGroup.setText("Add Group") + self.addGroup.setIconText(str("\U0001F4C2")) + self.removeEntry = QAction() + self.removeEntry.setText("Remove Entry") + self.removeEntry.setIconText("-") + self.removeEntry.triggered.connect(self.slot_remove_entry) + addEntryButton = QToolButton() + addEntryButton.setDefaultAction(self.addEntry) + buttonLayout.addWidget(addEntryButton) + addGroupButton = QToolButton() + addGroupButton.setDefaultAction(self.addGroup) + buttonLayout.addWidget(addGroupButton) + removeEntryButton = QToolButton() + removeEntryButton.setDefaultAction(self.removeEntry) + buttonLayout.addWidget(removeEntryButton) # QActions self.extra = QToolButton() self.editPaletteData = QAction() self.editPaletteData.setText("Edit Palette Settings") self.editPaletteData.triggered.connect(self.slot_edit_palette_data) self.extra.setDefaultAction(self.editPaletteData) buttonLayout.addWidget(self.extra) - self.actionMenu = QMenu() self.exportToGimp = QAction() self.exportToGimp.setText("Export as GIMP palette file.") self.exportToGimp.triggered.connect(self.slot_export_to_gimp_palette) self.exportToInkscape = QAction() self.exportToInkscape.setText("Export as Inkscape SVG with swatches.") self.exportToInkscape.triggered.connect(self.slot_export_to_inkscape_svg) + self.sortColors = QAction() + self.sortColors.setText("Sort colors") + self.sortColors.triggered.connect(self.slot_sort_colors) self.actionMenu.addAction(self.editPaletteData) self.actionMenu.addAction(self.exportToGimp) self.actionMenu.addAction(self.exportToInkscape) + self.actionMenu.addAction(self.sortColors) self.extra.setMenu(self.actionMenu) layout.addLayout(buttonLayout) self.slot_fill_combobox() self.setWidget(widget) # add widget to the docker def slot_paletteChanged(self, name): self.currentPalette = Palette(Application.resources("palette")[name]) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() @pyqtSlot('KoColorSetEntry') def slot_swatchSelected(self, entry): print("entry " + entry.name) if (self.canvas()) is not None: if (self.canvas().view()) is not None: name = entry.name if len(entry.id) > 0: name = entry.id + " - " + entry.name if len(name) > 0: if name in self.colorList: self.colorComboBox.setCurrentIndex(self.colorList.index(name)) color = self.currentPalette.colorForEntry(entry) self.canvas().view().setForeGroundColor(color) ''' A function for making a combobox with the available colors. We use QCompleter on the colorComboBox so that people can type in the name of a color to select it. This is useful for people with carefully made palettes where the colors are named properly, which makes it easier for them to find colors. ''' def slot_fill_combobox(self): if self.currentPalette is None: pass palette = self.currentPalette self.colorComboBox.clear() self.colorList.clear() for i in range(palette.colorsCountTotal()): entry = palette.colorSetEntryByIndex(i) color = palette.colorForEntry(entry).colorForCanvas(self.canvas()) colorSquare = QPixmap(12, 12) if entry.spotColor is True: img = colorSquare.toImage() circlePainter = QPainter() img.fill(self.colorComboBox.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(color) circlePainter.setBrush(brush) + circlePainter.pen().setWidth(0) circlePainter.drawEllipse(0, 0, 11, 11) circlePainter.end() colorSquare = QPixmap.fromImage(img) else: colorSquare.fill(color) name = entry.name if len(entry.id) > 0: name = entry.id + " - " + entry.name self.colorList.append(name) self.colorComboBox.addItem(QIcon(colorSquare), name) self.colorComboBox.setEditable(True) self.colorComboBox.setInsertPolicy(QComboBox.NoInsert) self.colorComboBox.completer().setCompletionMode(QCompleter.PopupCompletion) self.colorComboBox.completer().setCaseSensitivity(False) self.colorComboBox.completer().setFilterMode(Qt.MatchContains) self.colorComboBox.currentIndexChanged.connect(self.slot_get_color_from_combobox) def slot_get_color_from_combobox(self): if self.currentPalette is not None: entry = self.currentPalette.colorSetEntryByIndex(self.colorComboBox.currentIndex()) self.slot_swatchSelected(entry) def slot_add_entry(self): if (self.canvas()) is not None: if (self.canvas().view()) is not None: color = self.canvas().view().foreGroundColor() succes = self.paletteView.addEntryWithDialog(color) if succes is True: self.slot_fill_combobox() def slot_add_group(self): succes = self.paletteView.addGroupWithDialog() if succes is True: self.slot_fill_combobox() def slot_remove_entry(self): succes = self.paletteView.removeSelectedEntryWithDialog() if succes is True: self.slot_fill_combobox() ''' A function for giving a gui to edit palette metadata... I also want this to be the way to edit the settings of the palette docker. ''' def slot_edit_palette_data(self): dialog = QDialog(self) tabWidget = QTabWidget() dialog.setWindowTitle("Edit Palette Data") dialog.setLayout(QVBoxLayout()) dialog.layout().addWidget(tabWidget) paletteWidget = QWidget() paletteWidget.setLayout(QVBoxLayout()) tabWidget.addTab(paletteWidget, "Palette Data") paletteName = QLineEdit() paletteName.setText(self.cmb_palettes.currentText()) paletteWidget.layout().addWidget(paletteName) paletteColumns = QSpinBox() paletteColumns.setValue(self.currentPalette.columnCount()) paletteWidget.layout().addWidget(paletteColumns) paletteComment = QPlainTextEdit() paletteComment.appendPlainText(self.currentPalette.comment()) paletteWidget.layout().addWidget(paletteComment) buttons = QDialogButtonBox(QDialogButtonBox.Ok) dialog.layout().addWidget(buttons) buttons.accepted.connect(dialog.accept) # buttons.rejected.connect(dialog.reject()) if dialog.exec_() == QDialog.Accepted: Resource = Application.resources("palette")[self.cmb_palettes.currentText()] Resource.setName(paletteName.text()) self.currentPalette = Palette(Resource) print(paletteColumns.value()) self.currentPalette.setColumnCount(paletteColumns.value()) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() self.currentPalette.setComment(paletteComment.toPlainText()) + self.currentPalette.save() def slot_export_to_gimp_palette(self): palette_exporter_gimppalette.gimpPaletteExporter(self.cmb_palettes.currentText()) def slot_export_to_inkscape_svg(self): palette_exporter_inkscapeSVG.inkscapeSVGExporter(self.cmb_palettes.currentText()) + def slot_sort_colors(self): + colorSorter = palette_sortColors.sortColors(self.cmb_palettes.currentText()) + self.paletteView.setPalette(colorSorter.palette()) + def canvasChanged(self, canvas): pass # Add docker to the application :) Application.addDockWidgetFactory(DockWidgetFactory("palette_docker", DockWidgetFactoryBase.DockRight, Palette_Docker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_sortColors.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_sortColors.py new file mode 100644 index 0000000000..16a384d073 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_sortColors.py @@ -0,0 +1,69 @@ +''' +A script that sorts the colors in the group. +By Wolthera. +''' + + +# Importing the relevant dependancies: +import sys +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +import math +from krita import * + + +class sortColors: + + def __init__(self, name): + # We want people to select a palette... + allPalettes = Application.resources("palette") + self.paletteName = name + self.currentPalette = Palette(allPalettes[self.paletteName]) + self.sort_all_groups() + + def sort_all_groups(self): + self.sort_color_by_name(str()) + groupNames = self.currentPalette.groupNames() + for groupName in groupNames: + self.sort_color_by_name(groupName) + + def sort_color_by_name(self, groupName): + l = {} + colorCount = self.currentPalette.colorsCountGroup(groupName) + for i in range(colorCount - 1, -1, -1): + entry = self.currentPalette.colorSetEntryFromGroup((i), groupName) + l[entry.name + str(i)] = entry + self.currentPalette.removeEntry((i), groupName) + + for s in sorted(l): + self.currentPalette.addEntry(l[s], groupName) + + def sort_color_by_id(self, groupName): + l = {} + colorCount = self.currentPalette.colorsCountGroup(groupName) + for i in range(colorCount - 1, -1, -1): + entry = self.currentPalette.colorSetEntryFromGroup((i), groupName) + l[entry.id + " " + str(i)] = entry + self.currentPalette.removeEntry((i), groupName) + + for s in sorted(l): + self.currentPalette.addEntry(l[s], groupName) + + def sort_by_value(self, groupName): + l = {} + colorCount = self.currentPalette.colorsCountGroup(groupName) + for i in range(colorCount - 1, -1, -1): + entry = self.currentPalette.colorSetEntryFromGroup((i), groupName) + color = self.currentPalette.colorForEntry(entry) + color.setColorSpace("RGBA", "U8", "sRGB built-in") + l[color.components()[0] + color.components()[1] + color.components()[2]] = entry + self.currentPalette.removeEntry((i), groupName) + + for s in sorted(l): + self.currentPalette.addEntry(l[s], groupName) + + def sort_by_hue(self, stepsize, groupName): + pass + + def palette(self): + return self.currentPalette diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py index 0d88fbd7ea..da63ff9d85 100644 --- a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py @@ -1,2 +1,2 @@ - # let's make a module +# let's make a module from .quick_settings_docker import * diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop index a0fb1bf2bb..df08a76dbb 100644 --- a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -1,23 +1,25 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=quick_settings_docker X-Python-2-Compatible=false Name=Quick Settings Docker Name[ca]=Acoblador d'arranjament ràpid Name[ca@valencia]=Acoblador d'arranjament ràpid Name[es]=Panel de ajustes rápidos +Name[it]=Area di aggancio delle impostazioni rapide Name[nl]=Docker voor snelle instellingen Name[pt]=Área de Configuração Rápida Name[sv]=Dockningspanel med snabbinställningar Name[uk]=Панель швидких параметрів Name[x-test]=xxQuick Settings Dockerxx Comment=A Python-based docker for quickly changing brush size and opacity. Comment[ca]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat. Comment[ca@valencia]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat. Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. +Comment[it]=Un'area di aggancio basata su Python per cambiare rapidamente la dimensione del pennello e l'opacità. Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking. Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet. Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля. Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py index 6159c7c23b..45ad3de096 100644 --- a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py @@ -1,162 +1,162 @@ ''' Description: A Python based docker for quickly choosing the brushsize like similar dockers in other drawing programs. By Wolthera @package quick_settings_docker ''' # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * class QuickSettingsDocker(DockWidget): -# Init the docker + # Init the docker def __init__(self): super().__init__() # make base-widget and layout widget = QWidget() layout = QVBoxLayout() widget.setLayout(layout) self.setWindowTitle("Quick Settings Docker") tabWidget = QTabWidget() self.brushSizeTableView = QTableView() self.brushSizeTableView.verticalHeader().hide() self.brushSizeTableView.horizontalHeader().hide() self.brushSizeTableView.setSelectionMode(QTableView.SingleSelection) self.brushOpacityTableView = QTableView() self.brushOpacityTableView.verticalHeader().hide() self.brushOpacityTableView.horizontalHeader().hide() self.brushOpacityTableView.setSelectionMode(QTableView.SingleSelection) self.brushFlowTableView = QTableView() self.brushFlowTableView.verticalHeader().hide() self.brushFlowTableView.horizontalHeader().hide() self.brushFlowTableView.setSelectionMode(QTableView.SingleSelection) tabWidget.addTab(self.brushSizeTableView, "Size") tabWidget.addTab(self.brushOpacityTableView, "Opacity") tabWidget.addTab(self.brushFlowTableView, "Flow") layout.addWidget(tabWidget) self.setWidget(widget) # Add the widget to the docker. # amount of columns in each row for all the tables. self.columns = 4 # We want a grid with possible options to select. # To do this, we'll make a TableView widget and use a standarditemmodel for the entries. # The entries are filled out based on the sizes and opacity lists. # Sizes and opacity lists. The former is half-way copied from ptsai, the latter is based on personal experience of useful opacities. self.sizesList = [0.7, 1.0, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 25, 30, 35, 40, 50, 60, 70, 80, 100, 120, 160, 200, 250, 300, 350, 400, 450, 500] self.opacityList = [0.1, 0.5, 1, 5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100] self.brushSizeModel = QStandardItemModel((len(self.sizesList) / self.columns) + 1, self.columns) self.brushOpacityModel = QStandardItemModel((len(self.opacityList) / self.columns) + 1, self.columns) self.brushFlowModel = QStandardItemModel((len(self.opacityList) / self.columns) + 1, self.columns) self.fillSizesModel() self.fillOpacityModel() # Now we're done filling out our tables, we connect the views to the functions that'll change the settings. self.brushSizeTableView.clicked.connect(self.setBrushSize) self.brushOpacityTableView.clicked.connect(self.setBrushOpacity) self.brushFlowTableView.clicked.connect(self.setBrushFlow) def fillSizesModel(self): # First we empty the old model. We might wanna use this function in the future to fill it with the brushmask of the selected brush, but there's currently no API for recognising changes in the current brush nor is there a way to get its brushmask. self.brushSizeModel.clear() for s in range(len(self.sizesList)): # we're gonna itterate over our list, and make a new item for each entry. # We need to disable a bunch of stuff to make sure people won't do funny things to our entries. item = QStandardItem() item.setCheckable(False) item.setEditable(False) item.setDragEnabled(False) item.setText(str(self.sizesList[s])) # And from here on we'll make an icon. brushImage = QPixmap(32, 32) img = brushImage.toImage() circlePainter = QPainter() img.fill(self.brushSizeTableView.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(self.brushSizeTableView.palette().color(QPalette.Text)) circlePainter.setBrush(brush) circlePainter.pen().setWidth(0) brushSize = max(min((self.sizesList[s] / 500) * 100, 32), 1) brushSize = brushSize * 0.5 circlePainter.drawEllipse(QPointF(16, 16), brushSize, brushSize) circlePainter.end() brushImage = QPixmap.fromImage(img) # now we're done with drawing the icon, so we set it on the item. item.setIcon(QIcon(brushImage)) self.brushSizeModel.setItem(s / 4, s % 4, item) self.brushSizeTableView.setModel(self.brushSizeModel) self.brushSizeTableView.resizeColumnsToContents() def fillOpacityModel(self): self.brushOpacityModel.clear() self.brushFlowModel.clear() for s in range(len(self.opacityList)): # we're gonna itterate over our list, and make a new item for each entry. item = QStandardItem() item.setCheckable(False) item.setEditable(False) item.setDragEnabled(False) item.setText(str(self.opacityList[s])) brushImage = QPixmap(32, 32) img = brushImage.toImage() circlePainter = QPainter() img.fill(self.brushSizeTableView.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(self.brushSizeTableView.palette().color(QPalette.Text)) circlePainter.setBrush(brush) circlePainter.pen().setWidth(0) circlePainter.setOpacity(self.opacityList[s] / 100) circlePainter.drawEllipse(QPointF(16, 16), 16, 16) circlePainter.end() brushImage = QPixmap.fromImage(img) item.setIcon(QIcon(brushImage)) # the flow and opacity models will use virtually the same items, but Qt would like us to make sure we understand # these are not really the same items, so hence the clone. itemFlow = item.clone() self.brushOpacityModel.setItem(s / 4, s % 4, item) self.brushFlowModel.setItem(s / 4, s % 4, itemFlow) self.brushOpacityTableView.setModel(self.brushOpacityModel) self.brushFlowTableView.setModel(self.brushFlowModel) self.brushFlowTableView.resizeColumnsToContents() self.brushOpacityTableView.resizeColumnsToContents() def canvasChanged(self, canvas): pass @pyqtSlot('QModelIndex') def setBrushSize(self, index): i = index.column() + (index.row() * self.columns) brushSize = self.sizesList[i] if Application.activeWindow() and len(Application.activeWindow().views()) > 0: Application.activeWindow().views()[0].setBrushSize(brushSize) @pyqtSlot('QModelIndex') def setBrushOpacity(self, index): i = index.column() + (index.row() * self.columns) brushOpacity = self.opacityList[i] / 100 if Application.activeWindow() and len(Application.activeWindow().views()) > 0: Application.activeWindow().views()[0].setPaintingOpacity(brushOpacity) @pyqtSlot('QModelIndex') def setBrushFlow(self, index): i = index.column() + (index.row() * self.columns) brushOpacity = self.opacityList[i] / 100 if Application.activeWindow() and len(Application.activeWindow().views()) > 0: Application.activeWindow().views()[0].setPaintingFlow(brushOpacity) # Add docker to the application :) Application.addDockWidgetFactory(DockWidgetFactory("quick_settings_docker", DockWidgetFactoryBase.DockRight, QuickSettingsDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/scriptdocker/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/__init__.py new file mode 100644 index 0000000000..96bc1528e4 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/__init__.py @@ -0,0 +1,2 @@ +# let's make a module +from .scriptdocker import * diff --git a/plugins/extensions/pykrita/plugin/plugins/scriptdocker/kritapykrita_scriptdocker.desktop b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/kritapykrita_scriptdocker.desktop new file mode 100644 index 0000000000..a0bbb03732 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/kritapykrita_scriptdocker.desktop @@ -0,0 +1,23 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=scriptdocker +X-Python-2-Compatible=false +Name=Script Docker +Name[ca]=Acoblador de scripts +Name[ca@valencia]=Acoblador de scripts +Name[it]=Area di aggancio degli script +Name[nl]=Vastzetten van scripts +Name[pt]=Área de Programas +Name[sv]=Dockningsfönster för skript +Name[uk]=Бічна панель скриптів +Name[x-test]=xxScript Dockerxx +Comment=A Python-based docker for create actions and point to Python scripts +Comment[ca]=Un acoblador basant en el Python per crear accions i apuntar a scripts del Python +Comment[ca@valencia]=Un acoblador basant en el Python per crear accions i apuntar a scripts del Python +Comment[it]=Un'area di aggancio basata su Python per creare azioni e scegliere sctipt Python +Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts +Comment[pt]=Uma área acoplável, feita em Python, para criar acções e apontar para programas em Python +Comment[sv]=Ett Python-baserat dockningsfönster för att skapa åtgärder och peka ut Python-skript +Comment[uk]=Бічна панель для створення дій і керування скриптами на основі Python. +Comment[x-test]=xxA Python-based docker for create actions and point to Python scriptsxx diff --git a/plugins/extensions/pykrita/plugin/plugins/scriptdocker/scriptdocker.py b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/scriptdocker.py new file mode 100644 index 0000000000..00ccb1f519 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/scriptdocker.py @@ -0,0 +1,55 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QListView, QFormLayout, + QHBoxLayout, QPushButton, QLineEdit, QListWidget) +from PyQt5.QtCore import QObject +import krita + + +class ScriptDocker(krita.DockWidget): + + def __init__(self): + super(ScriptDocker, self).__init__() + + self.baseWidget = QWidget() + self.layout = QVBoxLayout() + self.scriptsLayout = QFormLayout() + self.addButton = QPushButton("Add Script") + self.actions = [] + + self.layout.addLayout(self.scriptsLayout) + self.layout.addWidget(self.addButton) + self.baseWidget.setLayout(self.layout) + self.setWidget(self.baseWidget) + + self.setWindowTitle("Script Docker") + self.addButton.clicked.connect(self.addNewRow) + + def canvasChanged(self, canvas): + pass + + def addNewRow(self): + directorySelectorLayout = QHBoxLayout() + directoryTextField = QLineEdit() + directoryDialogButton = QPushButton("...") + + directoryDialogButton.clicked.connect(self.test) + + directorySelectorLayout.addWidget(directoryTextField) + directorySelectorLayout.addWidget(directoryDialogButton) + + self.scriptsLayout.addRow("Script {0}".format(self.scriptsLayout.rowCount() + 1), directorySelectorLayout) + + def test(self): + obj = self.sender() + print('button', obj) + + def loadActions(self): + pass + + def readSettings(self): + pass + + def writeSettings(self): + pass + + +Application.addDockWidgetFactory(krita.DockWidgetFactory("scriptdocker", krita.DockWidgetFactoryBase.DockRight, ScriptDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py index 3b3f134d2b..cedb3de7a0 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py @@ -1,48 +1,66 @@ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QIcon, QKeySequence from PyQt5.QtCore import Qt import sys from . import docwrapper import os from scripter import resources_rc +import importlib class RunAction(QAction): def __init__(self, scripter, parent=None): super(RunAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor - self.output = self.scripter.uicontroller.findTabWidget('OutPut') + self.output = self.scripter.uicontroller.findTabWidget('OutPut', 'OutPutTextEdit') self.triggered.connect(self.run) self.setText('Run') self.setToolTip('Run Ctrl+R') self.setIcon(QIcon(':/icons/run.svg')) self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) @property def parent(self): return 'toolBar' def run(self): + """ This method execute python code from an activeDocument (file) or direct + from editor (ui_scripter/editor/pythoneditor.py). When executing code + from a file, we use importlib to load this module/file and with + "users_script" name. That's implementation seeks for a "main()" function in the script. + When executing code from editor without creating a file, we compile + this script to bytecode and we execute this in an empty scope. That's + faster than use exec directly and cleaner, because we are using an empty scope. """ + self.scripter.uicontroller.setActiveWidget('OutPut') stdout = sys.stdout stderr = sys.stderr output = docwrapper.DocWrapper(self.output.document()) output.write("======================================\n") sys.stdout = output sys.stderr = output script = self.editor.document().toPlainText() + document = self.scripter.documentcontroller.activeDocument try: - exec(script) + if document: + spec = importlib.util.spec_from_file_location("users_script", document.filePath) + users_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(users_module) + users_module.main() + else: + code = compile(script, '', 'exec') + exec(script, {}) except Exception as e: self.scripter.uicontroller.showException(str(e)) + sys.stdout = stdout sys.stderr = stderr # scroll to bottom of output max = self.output.verticalScrollBar().maximum() self.output.verticalScrollBar().setValue(max) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/clearaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/clearaction.py new file mode 100644 index 0000000000..884f1ff62f --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/clearaction.py @@ -0,0 +1,20 @@ +from PyQt5.QtWidgets import QAction +from PyQt5.QtGui import QIcon +from scripter import resources_rc + + +class ClearAction(QAction): + + def __init__(self, scripter, toolbar, parent=None): + super(ClearAction, self).__init__(parent) + self.scripter = scripter + self.toolbar = toolbar + + self.triggered.connect(self.clear) + + self.setText('Clear') + # path to the icon + # self.setIcon(QIcon(':/icons/clear.svg')) + + def clear(self): + self.toolbar.outputtextedit.clear() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputtextedit.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputtextedit.py new file mode 100644 index 0000000000..bdd5f69cf5 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputtextedit.py @@ -0,0 +1,10 @@ +from PyQt5.QtWidgets import QPlainTextEdit + + +class OutPutTextEdit(QPlainTextEdit): + + def __init__(self, scripter, toolbar, parent=None): + super(OutPutTextEdit, self).__init__(parent) + + self.setObjectName('OutPutTextEdit') + self.setReadOnly(True) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputwidget.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputwidget.py index 6a6dc6f68b..e6394c1cf2 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputwidget.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputwidget.py @@ -1,10 +1,22 @@ -from PyQt5.QtWidgets import QPlainTextEdit +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolBar, QTableWidget, QAction +from . import clearaction, outputtextedit -class OutPutWidget(QPlainTextEdit): +class OutPutWidget(QWidget): def __init__(self, scripter, parent=None): super(OutPutWidget, self).__init__(parent) self.scripter = scripter self.setObjectName('OutPut') + self.layout = QVBoxLayout() + + self.toolbar = QToolBar() + self.clearAction = clearaction.ClearAction(self.scripter, self) + self.toolbar.addAction(self.clearAction) + + self.outputtextedit = outputtextedit.OutPutTextEdit(self.scripter, self) + + self.layout.addWidget(self.toolbar) + self.layout.addWidget(self.outputtextedit) + self.setLayout(self.layout) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py index ac48b4f735..c11d074fe8 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py @@ -1,189 +1,191 @@ from PyQt5.QtGui import QTextCursor from PyQt5.QtWidgets import (QToolBar, QMenuBar, QTabWidget, QLabel, QVBoxLayout, QMessageBox, QSplitter) from PyQt5.QtCore import Qt, QObject from scripter.ui_scripter.syntax import syntax, syntaxstyles from scripter.ui_scripter.editor import pythoneditor from scripter import scripterdialog import os import importlib class UIController(object): def __init__(self): self.mainWidget = scripterdialog.ScripterDialog(self) self.actionToolbar = QToolBar('toolBar', self.mainWidget) self.menu_bar = QMenuBar(self.mainWidget) self.actionToolbar.setObjectName('toolBar') self.menu_bar.setObjectName('menuBar') self.actions = [] self.mainWidget.setWindowModality(Qt.NonModal) def initialize(self, scripter): self.editor = pythoneditor.CodeEditor(scripter) self.tabWidget = QTabWidget() self.statusBar = QLabel('untitled') self.splitter = QSplitter() self.splitter.setOrientation(Qt.Vertical) self.highlight = syntax.PythonHighlighter(self.editor.document(), syntaxstyles.DefaultSyntaxStyle()) self.scripter = scripter self.loadMenus() self.loadWidgets() self.loadActions() self._readSettings() vbox = QVBoxLayout(self.mainWidget) vbox.addWidget(self.menu_bar) vbox.addWidget(self.actionToolbar) self.splitter.addWidget(self.editor) self.splitter.addWidget(self.tabWidget) vbox.addWidget(self.splitter) vbox.addWidget(self.statusBar) self.mainWidget.resize(400, 500) self.mainWidget.setWindowTitle("Scripter") self.mainWidget.setSizeGripEnabled(True) self.mainWidget.show() self.mainWidget.activateWindow() def loadMenus(self): self.addMenu('File', 'File') def addMenu(self, menuName, parentName): parent = self.menu_bar.findChild(QObject, parentName) self.newMenu = None if parent: self.newMenu = parent.addMenu(menuName) else: self.newMenu = self.menu_bar.addMenu(menuName) self.newMenu.setObjectName(menuName) return self.newMenu def loadActions(self): module_path = 'scripter.ui_scripter.actions' actions_module = importlib.import_module(module_path) modules = [] for class_path in actions_module.action_classes: _module, _klass = class_path.rsplit('.', maxsplit=1) modules.append(dict(module='{0}.{1}'.format(module_path, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) action_class = getattr(m, module['klass']) obj = action_class(self.scripter) parent = self.mainWidget.findChild(QObject, obj.parent) self.actions.append(dict(action=obj, parent=parent)) for action in self.actions: action['parent'].addAction(action['action']) def loadWidgets(self): modulePath = 'scripter.ui_scripter.tabwidgets' widgetsModule = importlib.import_module(modulePath) modules = [] for classPath in widgetsModule.widgetClasses: _module, _klass = classPath.rsplit('.', maxsplit=1) modules.append(dict(module='{0}.{1}'.format(modulePath, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) widgetClass = getattr(m, module['klass']) obj = widgetClass(self.scripter) self.tabWidget.addTab(obj, obj.objectName()) def invokeAction(self, actionName): for action in self.actions: if action['action'].objectName() == actionName: method = getattr(action['action'], actionName) if method: return method() - def findTabWidget(self, widgetName): + def findTabWidget(self, widgetName, childName=''): for index in range(self.tabWidget.count()): widget = self.tabWidget.widget(index) if widget.objectName() == widgetName: + if childName: + widget = widget.findChild(QObject, childName) return widget def showException(self, exception): QMessageBox.critical(self.editor, "Error running script", str(exception)) def setDocumentEditor(self, document): self.editor.clear() self.editor.moveCursor(QTextCursor.Start) self.editor.insertPlainText(document.data) self.editor.moveCursor(QTextCursor.End) def setStatusBar(self, value='untitled'): self.statusBar.setText(value) def setActiveWidget(self, widgetName): widget = self.findTabWidget(widgetName) if widget: self.tabWidget.setCurrentWidget(widget) def setStepped(self, status): self.editor.setStepped(status) def clearEditor(self): self.editor.clear() def repaintDebugArea(self): self.editor.repaintDebugArea() def closeScripter(self): self.mainWidget.close() def _writeSettings(self): """ _writeSettings is a method invoked when the scripter starts, making control inversion. Actions can implement a writeSettings method to save your own settings without this method to know about it. """ self.scripter.settings.beginGroup('scripter') document = self.scripter.documentcontroller.activeDocument if document: self.scripter.settings.setValue('activeDocumentPath', document.filePath) for action in self.actions: writeSettings = getattr(action['action'], "writeSettings", None) if callable(writeSettings): writeSettings() self.scripter.settings.endGroup() def _readSettings(self): """ It's similar to _writeSettings, but reading the settings when the ScripterDialog is closed. """ self.scripter.settings.beginGroup('scripter') activeDocumentPath = self.scripter.settings.value('activeDocumentPath', '') if activeDocumentPath: document = self.scripter.documentcontroller.openDocument(activeDocumentPath) self.setStatusBar(document.filePath) self.setDocumentEditor(document) for action in self.actions: readSettings = getattr(action['action'], "readSettings", None) if callable(readSettings): readSettings() self.scripter.settings.endGroup() def _saveSettings(self): self.scripter.settings.sync() diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/dropbutton.py b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/dropbutton.py new file mode 100644 index 0000000000..7e44dd9b1f --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/dropbutton.py @@ -0,0 +1,19 @@ +from PyQt5.QtWidgets import QPushButton +from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtCore import QSize + + +class DropButton(QPushButton): + + def __init__(self, parent): + super(DropButton, self).__init__(parent) + + self.presetChooser = None + + self.preset = None + self.setFixedSize(64, 64) + self.setIconSize(QSize(64, 64)) + + def selectPreset(self): + self.preset = self.presetChooser.currentPreset().name() + self.setIcon(QIcon(QPixmap.fromImage(self.presetChooser.currentPreset().image()))) diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py index e84b8a4705..89286b2bf4 100644 --- a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py +++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py @@ -1,112 +1,58 @@ -import sys -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from krita import * +import krita +from tenbrushes import uitenbrushes -class DropButton(QPushButton): +class TenBrushesExtension(krita.Extension): def __init__(self, parent): - super().__init__(parent) - self.setFixedSize(64, 64) - self.setIconSize(QSize(64, 64)) - self.preset = None + super(TenBrushesExtension, self).__init__(parent) - def selectPreset(self): - self.preset = self.presetChooser.currentPreset().name() - self.setIcon(QIcon(QPixmap.fromImage(self.presetChooser.currentPreset().image()))) - - -class TenBrushesExtension(Extension): - - def __init__(self, parent): - super().__init__(parent) - self.buttons = [] self.actions = [] + self.buttons = [] + self.selectedPresets = [] def setup(self): action = Application.createAction("ten_brushes", "Ten Brushes") action.setToolTip("Assign ten brush presets to ten shortcuts.") - action.triggered.connect(self.showDialog) + action.triggered.connect(self.initialize) + + self.readSettings() + self.loadActions() + + def initialize(self): + self.uitenbrushes = uitenbrushes.UITenBrushes() + self.uitenbrushes.initialize(self) + + def readSettings(self): + self.selectedPresets = Application.readSetting("", "tenbrushes", "").split(',') + + def writeSettings(self): + presets = [] + + for index, button in enumerate(self.buttons): + self.actions[index].preset = button.preset + presets.append(button.preset) + Application.writeSetting("", "tenbrushes", ','.join(map(str, presets))) - # Read the ten selected brush presets from the settings - selectedPresets = Application.readSetting("", "tenbrushes", "").split(',') + def loadActions(self): allPresets = Application.resources("preset") - # Setup up to ten actions and give them default shortcuts - j = 0 - self.actions = [] - for i in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']: - action = Application.createAction("activate_preset_" + i, "Activate Preset " + i) - # action.setVisible(False) + + for index, item in enumerate(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']): + action = Application.createAction("activate_preset_" + item, "Activate Brush Preset " + item) action.setMenu("None") action.triggered.connect(self.activatePreset) - if j < len(selectedPresets) and selectedPresets[j] in allPresets: - action.preset = selectedPresets[j] + + if index < len(self.selectedPresets) and self.selectedPresets[index] in allPresets: + action.preset = self.selectedPresets[index] else: action.preset = None + self.actions.append(action) - j = j + 1 def activatePreset(self): - print("activatePreset", self.sender().preset) allPresets = Application.resources("preset") if Application.activeWindow() and len(Application.activeWindow().views()) > 0 and self.sender().preset in allPresets: Application.activeWindow().views()[0].activateResource(allPresets[self.sender().preset]) - def showDialog(self): - self.dialog = QDialog(Application.activeWindow().qwindow()) - - self.buttonBox = QDialogButtonBox(self.dialog) - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.dialog.reject) - - vbox = QVBoxLayout(self.dialog) - hbox = QHBoxLayout(self.dialog) - - self.presetChooser = PresetChooser(self.dialog) - - allPresets = Application.resources("preset") - j = 0 - self.buttons = [] - for i in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']: - buttonBox = QVBoxLayout() - button = DropButton(self.dialog) - button.setObjectName(i) - button.clicked.connect(button.selectPreset) - button.presetChooser = self.presetChooser - - if self.actions[j] and self.actions[j].preset and self.actions[j].preset in allPresets: - p = allPresets[self.actions[j].preset] - button.preset = p.name() - button.setIcon(QIcon(QPixmap.fromImage(p.image()))) - - buttonBox.addWidget(button) - label = QLabel("Ctrl+Alt+" + i) - label.setAlignment(Qt.AlignHCenter) - buttonBox.addWidget(label) - hbox.addLayout(buttonBox) - self.buttons.append(button) - j = j + 1 - - vbox.addLayout(hbox) - vbox.addWidget(self.presetChooser) - vbox.addWidget(self.buttonBox) - vbox.addWidget(QLabel("Select the brush preset, then click on the button you want to use to select the preset")) - - self.dialog.show() - self.dialog.activateWindow() - self.dialog.exec_() - - def accept(self): - i = 0 - presets = [] - for button in self.buttons: - self.actions[i].preset = button.preset - presets.append(button.preset) - i = i + 1 - Application.writeSetting("", "tenbrushes", ','.join(map(str, presets))) - self.dialog.accept() Scripter.addExtension(TenBrushesExtension(Application)) diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushesdialog.py b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushesdialog.py new file mode 100644 index 0000000000..9fdddbcbe4 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushesdialog.py @@ -0,0 +1,17 @@ +from PyQt5.QtWidgets import QDialog + + +class TenBrushesDialog(QDialog): + + def __init__(self, uitenbrushes, parent=None): + super(TenBrushesDialog, self).__init__(parent) + + self.uitenbrushes = uitenbrushes + + def accept(self): + self.uitenbrushes.tentbrushes.writeSettings() + + super(TenBrushesDialog, self).accept() + + def closeEvent(self, event): + event.accept() diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/uitenbrushes.py b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/uitenbrushes.py new file mode 100644 index 0000000000..05381a7d86 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/uitenbrushes.py @@ -0,0 +1,63 @@ +from PyQt5.QtCore import Qt, QSize +from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtWidgets import (QDialogButtonBox, QLabel, QVBoxLayout, QHBoxLayout) +from tenbrushes import tenbrushesdialog, dropbutton +import krita + + +class UITenBrushes(object): + + def __init__(self): + self.kritaInstance = krita.Krita.instance() + self.mainDialog = tenbrushesdialog.TenBrushesDialog(self, self.kritaInstance.activeWindow().qwindow()) + + self.buttonBox = QDialogButtonBox(self.mainDialog) + self.vbox = QVBoxLayout(self.mainDialog) + self.hbox = QHBoxLayout(self.mainDialog) + + self.buttonBox.accepted.connect(self.mainDialog.accept) + self.buttonBox.rejected.connect(self.mainDialog.reject) + + self.buttonBox.setOrientation(Qt.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + self.presetChooser = krita.PresetChooser(self.mainDialog) + + def initialize(self, tentbrushes): + self.tentbrushes = tentbrushes + + self.loadButtons() + + self.vbox.addLayout(self.hbox) + self.vbox.addWidget(self.presetChooser) + self.vbox.addWidget(self.buttonBox) + self.vbox.addWidget(QLabel("Select the brush preset, then click on the button you want to use to select the preset")) + + self.mainDialog.show() + self.mainDialog.activateWindow() + self.mainDialog.exec_() + + def loadButtons(self): + self.tentbrushes.buttons = [] + + allPresets = Application.resources("preset") + + for index, item in enumerate(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']): + buttonLayout = QVBoxLayout() + button = dropbutton.DropButton(self.mainDialog) + button.setObjectName(item) + button.clicked.connect(button.selectPreset) + button.presetChooser = self.presetChooser + + if self.tentbrushes.actions[index] and self.tentbrushes.actions[index].preset and self.tentbrushes.actions[index].preset in allPresets: + p = allPresets[self.tentbrushes.actions[index].preset] + button.preset = p.name() + button.setIcon(QIcon(QPixmap.fromImage(p.image()))) + + buttonLayout.addWidget(button) + label = QLabel("Ctrl+Alt+" + item) + label.setAlignment(Qt.AlignHCenter) + buttonLayout.addWidget(label) + + self.hbox.addLayout(buttonLayout) + self.tentbrushes.buttons.append(button) diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp index 52050d6491..7bad113285 100644 --- a/plugins/extensions/pykrita/plugin/utilities.cpp +++ b/plugins/extensions/pykrita/plugin/utilities.cpp @@ -1,546 +1,607 @@ // This file is part of PyKrita, Krita' Python scripting plugin. // // Copyright (C) 2006 Paul Giannaros // Copyright (C) 2012, 2013 Shaheed Haque // // 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) version 3, or any // later version accepted by the membership of KDE e.V. (or its // successor approved by the membership of KDE e.V.), which shall // act as a proxy defined in Section 6 of version 3 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 library. If not, see . // // config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so // on the build system #include "config.h" #include "utilities.h" #include #include #include +#include #include #include #include +#include #include #include #include +#include #include #define THREADED 1 namespace PyKrita { namespace { #ifndef Q_OS_WIN QLibrary* s_pythonLibrary = 0; #endif PyThreadState* s_pythonThreadState = 0; } // anonymous namespace const char* Python::PYKRITA_ENGINE = "pykrita"; Python::Python() { #if THREADED m_state = PyGILState_Ensure(); #endif } Python::~Python() { #if THREADED PyGILState_Release(m_state); #endif } bool Python::prependStringToList(PyObject* const list, const QString& value) { PyObject* const u = unicode(value); bool result = !PyList_Insert(list, 0, u); Py_DECREF(u); if (!result) traceback(QString("Failed to prepend %1").arg(value)); return result; } bool Python::functionCall(const char* const functionName, const char* const moduleName) { PyObject* const result = functionCall(functionName, moduleName, PyTuple_New(0)); if (result) Py_DECREF(result); return bool(result); } PyObject* Python::functionCall( const char* const functionName , const char* const moduleName , PyObject* const arguments ) { if (!arguments) { errScript << "Missing arguments for" << moduleName << functionName; return 0; } PyObject* const func = itemString(functionName, moduleName); if (!func) { errScript << "Failed to resolve" << moduleName << functionName; return 0; } if (!PyCallable_Check(func)) { traceback(QString("Not callable %1.%2").arg(moduleName).arg(functionName)); return 0; } PyObject* const result = PyObject_CallObject(func, arguments); Py_DECREF(arguments); if (!result) traceback(QString("No result from %1.%2").arg(moduleName).arg(functionName)); return result; } bool Python::itemStringDel(const char* const item, const char* const moduleName) { PyObject* const dict = moduleDict(moduleName); const bool result = dict && PyDict_DelItemString(dict, item); if (!result) traceback(QString("Could not delete item string %1.%2").arg(moduleName).arg(item)); return result; } PyObject* Python::itemString(const char* const item, const char* const moduleName) { if (PyObject* const value = itemString(item, moduleDict(moduleName))) return value; errScript << "Could not get item string" << moduleName << item; return 0; } PyObject* Python::itemString(const char* item, PyObject* dict) { if (dict) if (PyObject* const value = PyDict_GetItemString(dict, item)) return value; traceback(QString("Could not get item string %1").arg(item)); return 0; } bool Python::itemStringSet(const char* const item, PyObject* const value, const char* const moduleName) { PyObject* const dict = moduleDict(moduleName); const bool result = dict && !PyDict_SetItemString(dict, item, value); if (!result) traceback(QString("Could not set item string %1.%2").arg(moduleName).arg(item)); return result; } PyObject* Python::kritaHandler(const char* const moduleName, const char* const handler) { if (PyObject* const module = moduleImport(moduleName)) return functionCall(handler, "krita", Py_BuildValue("(O)", module)); return 0; } QString Python::lastTraceback() const { QString result; result.swap(m_traceback); return result; } -void Python::libraryLoad() +bool Python::libraryLoad() { -#ifdef Q_OS_WIN - if (Py_IsInitialized()) { - dbgScript << "Python interpreter is already initialized"; - } else { - dbgScript << "Initializing Python interpreter"; -#else + // no-op on Windows +#ifndef Q_OS_WIN if (!s_pythonLibrary) { dbgScript << "Creating s_pythonLibrary" << PYKRITA_PYTHON_LIBRARY; s_pythonLibrary = new QLibrary(PYKRITA_PYTHON_LIBRARY); - if (!s_pythonLibrary) + if (!s_pythonLibrary) { errScript << "Could not create" << PYKRITA_PYTHON_LIBRARY; + return false; + } s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint); - if (!s_pythonLibrary->load()) - errScript << "Could not load" << PYKRITA_PYTHON_LIBRARY; + if (!s_pythonLibrary->load()) { + errScript << QString("Could not load %1 -- Reason: %2").arg(PYKRITA_PYTHON_LIBRARY).arg(s_pythonLibrary->errorString()); + return false; + } + } #endif + return true; +} - Py_InitializeEx(0); - if (!Py_IsInitialized()) { +bool Python::setPath(const QStringList& paths) +{ + if (Py_IsInitialized()) { + warnScript << "Setting paths when Python interpreter is already initialized"; + } #ifdef Q_OS_WIN - errScript << "Could not initialise Python interpreter"; + constexpr char pathSeparator = ';'; +#else + constexpr char pathSeparator = ':'; +#endif + QString joinedPaths = paths.join(pathSeparator); + // Append the default search path + // TODO: Properly handle embedded Python +#ifdef Q_OS_WIN + QString currentPaths; + // Find embeddable Python + // TODO: Don't hard-code the paths + QDir pythonDir(KoResourcePaths::getApplicationRoot()); + if (pythonDir.cd("python")) { + dbgScript << "Found embeddable Python at" << pythonDir.absolutePath(); + currentPaths = pythonDir.absolutePath() + pathSeparator + + pythonDir.absoluteFilePath("python36.zip"); + } else { +# if 1 + // Use local Python??? + currentPaths = QString::fromWCharArray(Py_GetPath()); + warnScript << "Embeddable Python not found."; + warnScript << "Default paths:" << currentPaths; +# else + // Or should we fail? + errScript << "Embeddable Python not found, not setting Python paths"; + return false; +# endif + } +#else + QString currentPaths = QString::fromLocal8Bit(qgetenv("PYTHONPATH")); +#endif + if (!currentPaths.isEmpty()) { + joinedPaths = joinedPaths + pathSeparator + currentPaths; + } + dbgScript << "Setting paths:" << joinedPaths; +#ifdef Q_OS_WIN + QVector joinedPathsWChars(joinedPaths.size() + 1, 0); + joinedPaths.toWCharArray(joinedPathsWChars.data()); + Py_SetPath(joinedPathsWChars.data()); #else - errScript << "Could not initialise" << PYKRITA_PYTHON_LIBRARY; + qputenv("PYTHONPATH", joinedPaths.toLocal8Bit()); #endif + return true; +} + +void Python::ensureInitialized() +{ + if (Py_IsInitialized()) { + warnScript << "Python interpreter is already initialized, not initializing again"; + } else { + dbgScript << "Initializing Python interpreter"; + Py_InitializeEx(0); + if (!Py_IsInitialized()) { + errScript << "Could not initialise Python interpreter"; } #if THREADED PyEval_InitThreads(); s_pythonThreadState = PyGILState_GetThisThreadState(); PyEval_ReleaseThread(s_pythonThreadState); #endif } } -void Python::libraryUnload() +void Python::maybeFinalize() { -#ifdef Q_OS_WIN - warnScript << "Explicitly unloading Python interpreter isn't supported for Windows"; - { -#else - if (s_pythonLibrary) { -#endif - // Shut the interpreter down if it has been started. - if (Py_IsInitialized()) { + if (!Py_IsInitialized()) { + warnScript << "Python interpreter not initialized, no need to finalize"; + } else { #if THREADED - PyEval_AcquireThread(s_pythonThreadState); + PyEval_AcquireThread(s_pythonThreadState); #endif - //Py_Finalize(); - } + Py_Finalize(); + } +} + +void Python::libraryUnload() +{ + // no-op on Windows #ifndef Q_OS_WIN + if (s_pythonLibrary) { + // Shut the interpreter down if it has been started. if (s_pythonLibrary->isLoaded()) { s_pythonLibrary->unload(); } delete s_pythonLibrary; s_pythonLibrary = 0; -#endif } +#endif } PyObject* Python::moduleActions(const char* moduleName) { return kritaHandler(moduleName, "moduleGetActions"); } PyObject* Python::moduleConfigPages(const char* const moduleName) { return kritaHandler(moduleName, "moduleGetConfigPages"); } QString Python::moduleHelp(const char* moduleName) { QString r; PyObject* const result = kritaHandler(moduleName, "moduleGetHelp"); if (result) { r = unicode(result); Py_DECREF(result); } return r; } PyObject* Python::moduleDict(const char* const moduleName) { PyObject* const module = moduleImport(moduleName); if (module) if (PyObject* const dictionary = PyModule_GetDict(module)) return dictionary; traceback(QString("Could not get dict %1").arg(moduleName)); return 0; } PyObject* Python::moduleImport(const char* const moduleName) { PyObject* const module = PyImport_ImportModule(moduleName); if (module) return module; traceback(QString("Could not import %1").arg(moduleName)); return 0; } void* Python::objectUnwrap(PyObject* o) { PyObject* const arguments = Py_BuildValue("(O)", o); PyObject* const result = functionCall("unwrapinstance", "sip", arguments); if (!result) return 0; void* const r = reinterpret_cast(ptrdiff_t(PyLong_AsLongLong(result))); Py_DECREF(result); return r; } PyObject* Python::objectWrap(void* const o, const QString& fullClassName) { const QString classModuleName = fullClassName.section('.', 0, -2); const QString className = fullClassName.section('.', -1); PyObject* const classObject = itemString(PQ(className), PQ(classModuleName)); if (!classObject) return 0; PyObject* const arguments = Py_BuildValue("NO", PyLong_FromVoidPtr(o), classObject); PyObject* const result = functionCall("wrapinstance", "sip", arguments); return result; } // Inspired by http://www.gossamer-threads.com/lists/python/python/150924. void Python::traceback(const QString& description) { m_traceback.clear(); if (!PyErr_Occurred()) // Return an empty string on no error. // NOTE "Return a string?" really?? return; PyObject* exc_typ; PyObject* exc_val; PyObject* exc_tb; PyErr_Fetch(&exc_typ, &exc_val, &exc_tb); PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb); // Include the traceback. if (exc_tb) { m_traceback = "Traceback (most recent call last):\n"; PyObject* const arguments = PyTuple_New(1); PyTuple_SetItem(arguments, 0, exc_tb); PyObject* const result = functionCall("format_tb", "traceback", arguments); if (result) { for (int i = 0, j = PyList_Size(result); i < j; i++) { PyObject* const tt = PyList_GetItem(result, i); PyObject* const t = Py_BuildValue("(O)", tt); char* buffer; if (!PyArg_ParseTuple(t, "s", &buffer)) break; m_traceback += buffer; } Py_DECREF(result); } Py_DECREF(exc_tb); } // Include the exception type and value. if (exc_typ) { PyObject* const temp = PyObject_GetAttrString(exc_typ, "__name__"); if (temp) { m_traceback += unicode(temp); m_traceback += ": "; } Py_DECREF(exc_typ); } if (exc_val) { PyObject* const temp = PyObject_Str(exc_val); if (temp) { m_traceback += unicode(temp); m_traceback += "\n"; } Py_DECREF(exc_val); } m_traceback += description; QStringList l = m_traceback.split("\n"); Q_FOREACH(const QString &s, l) { errScript << s; } /// \todo How about to show it somewhere else than "console output"? } PyObject* Python::unicode(const QString& string) { #if PY_MAJOR_VERSION < 3 /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */ PyObject* s = PyString_FromString(PQ(string)); PyObject* u = PyUnicode_FromEncodedObject(s, "utf-8", "strict"); Py_DECREF(s); return u; #elif PY_MINOR_VERSION < 3 /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ # ifdef Py_UNICODE_WIDE return PyUnicode_DecodeUTF16((const char*)string.constData(), string.length() * 2, 0, 0); # else return PyUnicode_FromUnicode(string.constData(), string.length()); # endif #else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, string.constData(), string.length()); #endif } QString Python::unicode(PyObject* const string) { #if PY_MAJOR_VERSION < 3 /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */ if (PyString_Check(string)) return QString(PyString_AsString(string)); else if (PyUnicode_Check(string)) { const int unichars = PyUnicode_GetSize(string); # ifdef HAVE_USABLE_WCHAR_T return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars); # else # ifdef Py_UNICODE_WIDE return QString::fromUcs4((const unsigned int*)PyUnicode_AsUnicode(string), unichars); # else return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars); # endif # endif } else return QString(); #elif PY_MINOR_VERSION < 3 /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ if (!PyUnicode_Check(string)) return QString(); const int unichars = PyUnicode_GetSize(string); # ifdef HAVE_USABLE_WCHAR_T return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars); # else # ifdef Py_UNICODE_WIDE return QString::fromUcs4(PyUnicode_AsUnicode(string), unichars); # else return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars); # endif # endif #else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ if (!PyUnicode_Check(string)) return QString(); const int unichars = PyUnicode_GetLength(string); if (0 != PyUnicode_READY(string)) return QString(); switch (PyUnicode_KIND(string)) { case PyUnicode_1BYTE_KIND: return QString::fromLatin1((const char*)PyUnicode_1BYTE_DATA(string), unichars); case PyUnicode_2BYTE_KIND: return QString::fromUtf16(PyUnicode_2BYTE_DATA(string), unichars); case PyUnicode_4BYTE_KIND: return QString::fromUcs4(PyUnicode_4BYTE_DATA(string), unichars); default: break; } return QString(); #endif } bool Python::isUnicode(PyObject* const string) { #if PY_MAJOR_VERSION < 3 return PyString_Check(string) || PyUnicode_Check(string); #else return PyUnicode_Check(string); #endif } void Python::updateConfigurationFromDictionary(KConfigBase* const config, PyObject* const dictionary) { PyObject* groupKey; PyObject* groupDictionary; Py_ssize_t position = 0; while (PyDict_Next(dictionary, &position, &groupKey, &groupDictionary)) { if (!isUnicode(groupKey)) { traceback(QString("Configuration group name not a string")); continue; } QString groupName = unicode(groupKey); if (!PyDict_Check(groupDictionary)) { traceback(QString("Configuration group %1 top level key not a dictionary").arg(groupName)); continue; } // There is a group per module. KConfigGroup group = config->group(groupName); PyObject* key; PyObject* value; Py_ssize_t x = 0; while (PyDict_Next(groupDictionary, &x, &key, &value)) { if (!isUnicode(key)) { traceback(QString("Configuration group %1 itemKey not a string").arg(groupName)); continue; } PyObject* arguments = Py_BuildValue("(Oi)", value, 0); PyObject* pickled = functionCall("dumps", "pickle", arguments); if (pickled) { #if PY_MAJOR_VERSION < 3 QString ascii(unicode(pickled)); #else QString ascii(PyBytes_AsString(pickled)); #endif group.writeEntry(unicode(key), ascii); Py_DECREF(pickled); } else { errScript << "Cannot write" << groupName << unicode(key) << unicode(PyObject_Str(value)); } } } } void Python::updateDictionaryFromConfiguration(PyObject* const dictionary, const KConfigBase* const config) { qDebug() << config->groupList(); Q_FOREACH(QString groupName, config->groupList()) { KConfigGroup group = config->group(groupName); PyObject* groupDictionary = PyDict_New(); PyDict_SetItemString(dictionary, PQ(groupName), groupDictionary); Q_FOREACH(QString key, group.keyList()) { QString pickled = group.readEntry(key); #if PY_MAJOR_VERSION < 3 PyObject* arguments = Py_BuildValue("(s)", PQ(pickled)); #else PyObject* arguments = Py_BuildValue("(y)", PQ(pickled)); #endif PyObject* value = functionCall("loads", "pickle", arguments); if (value) { PyDict_SetItemString(groupDictionary, PQ(key), value); Py_DECREF(value); } else { errScript << "Cannot read" << groupName << key << pickled; } } Py_DECREF(groupDictionary); } } bool Python::prependPythonPaths(const QString& path) { PyObject* sys_path = itemString("path", "sys"); return bool(sys_path) && prependPythonPaths(path, sys_path); } bool Python::prependPythonPaths(const QStringList& paths) { PyObject* sys_path = itemString("path", "sys"); if (!sys_path) return false; /// \todo Heh, boosts' range adaptors would be good here! QStringList reversed_paths; std::reverse_copy( paths.begin() , paths.end() , std::back_inserter(reversed_paths) ); Q_FOREACH(const QString & path, reversed_paths) if (!prependPythonPaths(path, sys_path)) return false; return true; } bool Python::prependPythonPaths(const QString& path, PyObject* sys_path) { Q_ASSERT("Dir entry expected to be valid" && sys_path); return bool(prependStringToList(sys_path, path)); } } // namespace PyKrita // krita: indent-width 4; diff --git a/plugins/extensions/pykrita/plugin/utilities.h b/plugins/extensions/pykrita/plugin/utilities.h index 5e60410c80..0324268a4a 100644 --- a/plugins/extensions/pykrita/plugin/utilities.h +++ b/plugins/extensions/pykrita/plugin/utilities.h @@ -1,229 +1,246 @@ // This file is part of PyKrita, Krita' Python scripting plugin. // // A couple of useful macros and functions used inside of pykrita_engine.cpp and pykrita_plugin.cpp. // // Copyright (C) 2006 Paul Giannaros // Copyright (C) 2012, 2013 Shaheed Haque // // 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) version 3, or any // later version accepted by the membership of KDE e.V. (or its // successor approved by the membership of KDE e.V.), which shall // act as a proxy defined in Section 6 of version 3 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 library. If not, see . // #ifndef __PYKRITA_UTILITIES_H__ # define __PYKRITA_UTILITIES_H__ #include #include #include class KConfigBase; /// Save us some ruddy time when printing out QStrings with UTF-8 # define PQ(x) x.toUtf8().constData() namespace PyKrita { /** * Instantiate this class on the stack to automatically get and release the * GIL. * * Also, making all the utility functions members of this class means that in * many cases the compiler tells us where the class in needed. In the remaining * cases (i.e. bare calls to the Python C API), inspection is used to needed * to add the requisite Python() object. To prevent this object being optimised * away in these cases due to lack of use, all instances have the form of an * assignment, e.g.: * * Python py = Python() * * This adds a little overhead, but this is a small price for consistency. */ class Python { public: Python(); ~Python(); /** - * Load the Python interpreter. + * Load the Python shared library. This does nothing on Windows. */ - static void libraryLoad(); + static bool libraryLoad(); /** - * Unload the Python interpreter. + * Set the Python paths by calling Py_SetPath. This should be called before + * initialization to ensure the proper libraries get loaded. + */ + static bool setPath(const QStringList& paths); + + /** + * Make sure the Python interpreter is initialized. Ideally should be only + * called once. + */ + static void ensureInitialized(); + + /** + * Finalize the Python interpreter. Not gauranteed to work. + */ + static void maybeFinalize(); + + /** + * Unload the Python shared library. This does nothing on Windows. */ static void libraryUnload(); /// Convert a QString to a Python unicode object. static PyObject* unicode(const QString& string); /// Convert a Python unicode object to a QString. static QString unicode(PyObject* string); /// Test if a Python object is compatible with a QString. static bool isUnicode(PyObject* string); /// Prepend a QString to a list as a Python unicode object bool prependStringToList(PyObject* list, const QString& value); /** * Print and save (see @ref lastTraceback()) the current traceback in a * form approximating what Python would print: * * Traceback (most recent call last): * File "/home/shahhaqu/.kde/share/apps/krita/pykrita/pluginmgr.py", line 13, in * import kdeui * ImportError: No module named kdeui * Could not import pluginmgr. */ void traceback(const QString& description); /** * Store the last traceback we handled using @ref traceback(). */ QString lastTraceback(void) const; /** * Create a Python dictionary from a KConfigBase instance, writing the * string representation of the values. */ void updateDictionaryFromConfiguration(PyObject* dictionary, const KConfigBase* config); /** * Write a Python dictionary to a configuration object, converting objects * to their string representation along the way. */ void updateConfigurationFromDictionary(KConfigBase* config, PyObject* dictionary); /** * Call the named module's named entry point. */ bool functionCall(const char* functionName, const char* moduleName = PYKRITA_ENGINE); PyObject* functionCall(const char* functionName, const char* moduleName, PyObject* arguments); /** * Delete the item from the named module's dictionary. */ bool itemStringDel(const char* item, const char* moduleName = PYKRITA_ENGINE); /** * Get the item from the named module's dictionary. * * @return 0 or a borrowed reference to the item. */ PyObject* itemString(const char* item, const char* moduleName = PYKRITA_ENGINE); /** * Get the item from the given dictionary. * * @return 0 or a borrowed reference to the item. */ PyObject* itemString(const char* item, PyObject* dict); /** * Set the item in the named module's dictionary. */ bool itemStringSet(const char* item, PyObject* value, const char* moduleName = PYKRITA_ENGINE); /** * Get the Actions defined by a module. The returned object is * [ { function, ( text, icon, shortcut, menu ) }... ] for each module * function decorated with @action. * * @return 0 or a new reference to the result. */ PyObject* moduleActions(const char* moduleName); /** * Get the ConfigPages defined by a module. The returned object is * [ { function, callable, ( name, fullName, icon ) }... ] for each module * function decorated with @configPage. * * @return 0 or a new reference to the result. */ PyObject* moduleConfigPages(const char* moduleName); /** * Get the named module's dictionary. * * @return 0 or a borrowed reference to the dictionary. */ PyObject* moduleDict(const char* moduleName = PYKRITA_ENGINE); /** * Get the help text defined by a module. */ QString moduleHelp(const char* moduleName); /** * Import the named module. * * @return 0 or a borrowed reference to the module. */ PyObject* moduleImport(const char* moduleName); /** * A void * for an arbitrary Qt/KDE object that has been wrapped by SIP. Nifty. * * @param o The object to be unwrapped. The reference is borrowed. */ void* objectUnwrap(PyObject* o); /** * A PyObject * for an arbitrary Qt/KDE object using SIP wrapping. Nifty. * * @param o The object to be wrapped. * @param className The full class name of o, e.g. "PyQt5.QtWidgets.QWidget". * @return @c 0 or a new reference to the object. */ PyObject* objectWrap(void* o, const QString& className); /** * Add a given path to to the front of \c PYTHONPATH * * @param path A string (path) to be added * @return @c true on success, @c false otherwise. */ bool prependPythonPaths(const QString& path); /** * Add listed paths to to the front of \c PYTHONPATH * * @param paths A string list (paths) to be added * @return @c true on success, @c false otherwise. */ bool prependPythonPaths(const QStringList& paths); static const char* PYKRITA_ENGINE; private: /// @internal Helper function for @c prependPythonPaths overloads bool prependPythonPaths(const QString&, PyObject*); PyGILState_STATE m_state; mutable QString m_traceback; /** * Run a handler function supplied by the krita module on another module. * * @return 0 or a new reference to the result. */ PyObject* kritaHandler(const char* moduleName, const char* handler); }; } // namespace PyKrita #endif // __PYKRITA_UTILITIES_H__ // krita: indent-width 4; diff --git a/plugins/extensions/pykrita/sip/krita/Action.sip b/plugins/extensions/pykrita/sip/krita/Action.sip index 8702596105..5f610c1eeb 100644 --- a/plugins/extensions/pykrita/sip/krita/Action.sip +++ b/plugins/extensions/pykrita/sip/krita/Action.sip @@ -1,37 +1,37 @@ class Action : QObject { %TypeHeaderCode #include "Action.h" %End + public: - Action(QObject* parent /TransferThis/ = 0); - Action(const QString & name, QAction* action, QObject* parent /TransferThis/ = 0); + explicit Action(QObject *parent /TransferThis/ = 0); + Action(const QString &name, QAction* action, QObject *parent /TransferThis/ = 0); virtual ~Action(); bool operator==(const Action &other) const; bool operator!=(const Action &other) const; public Q_SLOTS: QString text() const; - void settext(QString text); + void setText(QString text); QString name() const; void setName(QString value); bool isCheckable() const; void setCheckable(bool value); bool isChecked() const; void setChecked(bool value); QString shortcut() const; void setShortcut(QString value); bool isVisible() const; void setVisible(bool value); bool isEnabled() const; void setEnabled(bool value); void setToolTip(QString tooltip); void trigger(); void setMenu(const QString menu); QString menu() const; Q_SIGNALS: void triggered(bool); private: private: Action(const Action &); // Generated }; - diff --git a/plugins/extensions/pykrita/sip/krita/Document.sip b/plugins/extensions/pykrita/sip/krita/Document.sip index a96ce283a8..83102c4a7b 100644 --- a/plugins/extensions/pykrita/sip/krita/Document.sip +++ b/plugins/extensions/pykrita/sip/krita/Document.sip @@ -1,65 +1,69 @@ 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); + int xOffset() const; + void setXOffset(int x); + int yOffset() const; + void setYOffset(int y); 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 resizeImage(int x, int y, 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 e18a4aa32c..bec7bacf97 100644 --- a/plugins/extensions/pykrita/sip/krita/Krita.sip +++ b/plugins/extensions/pykrita/sip/krita/Krita.sip @@ -1,58 +1,61 @@ 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 colorModels() const; + QStringList colorDepths(const QString &colorModel) const; 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/; + QStringList recentDocuments() const; 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 &id, 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/Palette.sip b/plugins/extensions/pykrita/sip/krita/Palette.sip index 6c01aa2e6e..f32fc47b9a 100644 --- a/plugins/extensions/pykrita/sip/krita/Palette.sip +++ b/plugins/extensions/pykrita/sip/krita/Palette.sip @@ -1,38 +1,45 @@ struct KoColorSetEntry { %TypeHeaderCode #include "KoColorSet.h" %End public: KoColorSetEntry(); QString name; QString id; bool spotColor; bool operator==(const KoColorSetEntry& rhs) const; }; class Palette : QObject { %TypeHeaderCode #include "Palette.h" %End Palette(const Palette & __0); public: Palette(Resource *resource); int numberOfEntries() const; int columnCount(); void setColumnCount(int columns); QString comment(); void setComment(QString comment); QStringList groupNames(); bool addGroup(QString name); bool removeGroup(QString name, bool keepColors); int colorsCountTotal(); int colorsCountGroup(QString name); KoColorSetEntry colorSetEntryByIndex(int index); KoColorSetEntry colorSetEntryFromGroup(int index, const QString &groupName); ManagedColor *colorForEntry(KoColorSetEntry entry) /Factory/; + void addEntry(KoColorSetEntry entry, QString groupName); + void removeEntry(int index, const QString &groupName); + void insertEntry(int index, KoColorSetEntry entry, QString groupName); + bool editEntry(int index, KoColorSetEntry entry, QString groupName); + bool changeGroupName(QString oldGroupName, QString newGroupName); + bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore); + bool save(); private: }; diff --git a/plugins/extensions/pykrita/tests/__init__.py b/plugins/extensions/pykrita/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/extensions/pykrita/tests/actionTests/__init__.py b/plugins/extensions/pykrita/tests/actionTests/__init__.py new file mode 100644 index 0000000000..8df7667fb6 --- /dev/null +++ b/plugins/extensions/pykrita/tests/actionTests/__init__.py @@ -0,0 +1,6 @@ +import sys + + +inst_dir = sys.argv[2] +source_dir = sys.argv[3] +sys.path.insert(0, str("{0}/share/krita/pykrita/PyKrita").format(inst_dir)) diff --git a/plugins/extensions/pykrita/tests/actionTests/action_test.py b/plugins/extensions/pykrita/tests/actionTests/action_test.py new file mode 100644 index 0000000000..6ebc5590e6 --- /dev/null +++ b/plugins/extensions/pykrita/tests/actionTests/action_test.py @@ -0,0 +1,84 @@ +import unittest +from krita import Action +from PyQt5.QtWidgets import QAction + + +class TestAction(unittest.TestCase): + + def setUp(self): + self.instance = Action() + self.instance.triggered.connect(self.slotTriggered) + self.triggered = False + + def testConstructor(self): + self.assertEqual(bool(self.instance), True) + + def testConstructorWithStringQAction(self): + new_action = Action("test", QAction("test")) + self.assertEqual(bool(new_action), True) + + def testConstructorInvalidParameter(self): + with self.assertRaises(TypeError): + Action(str('')) + + def testEqualOperator(self): + new_action = self.instance + self.assertEqual(new_action == self.instance, True) + + def testInequalityOperator(self): + new_action = Action() + self.assertEqual(new_action != self.instance, True) + + def testTextProperties(self): + self.instance.setText("test") + self.assertEqual(self.instance.text() == "test", True) + + def testNameProperties(self): + self.instance.setName("test") + self.assertEqual(self.instance.name() == "test", True) + + def testCheckableInitialState(self): + self.assertEqual(self.instance.isCheckable(), False) + + def testCheckableToTrue(self): + self.instance.setCheckable(True) + self.assertEqual(self.instance.isCheckable(), True) + + def testCheckableToFalse(self): + self.instance.setCheckable(False) + self.assertEqual(self.instance.isCheckable(), False) + + def testCheckedInitialState(self): + self.assertEqual(self.instance.isChecked(), False) + + def testCheckedToTrue(self): + self.instance.setCheckable(True) + self.instance.setChecked(True) + self.assertEqual(self.instance.isChecked(), True) + + def testCheckedToFalse(self): + self.instance.setChecked(False) + self.assertEqual(self.instance.isChecked(), False) + + def testCheckedToFalseNotCheckable(self): + self.instance.setChecked(True) + self.assertEqual(self.instance.isChecked(), False) + + def testVisibleInitialState(self): + self.assertEqual(self.instance.isVisible(), True) + + def testVisibleToTrue(self): + self.instance.setVisible(True) + self.assertEqual(self.instance.isVisible(), True) + + def testVisibleToFalse(self): + self.instance.setVisible(False) + self.assertEqual(self.instance.isVisible(), False) + + def testTrigger(self): + self.instance.trigger() + self.assertEqual(self.triggered, True) + + # helper method + def slotTriggered(self): + self.triggered = True diff --git a/plugins/extensions/pykrita/tests/actionTests/extension_test.py b/plugins/extensions/pykrita/tests/actionTests/extension_test.py new file mode 100644 index 0000000000..9ae63c2a9b --- /dev/null +++ b/plugins/extensions/pykrita/tests/actionTests/extension_test.py @@ -0,0 +1,30 @@ +import unittest +from krita import Extension +from PyQt5.QtCore import QObject + + +class TestExtension(unittest.TestCase): + + def testConstructorSubClass(self): + self.assertEqual(bool(SubClassExtension()), True) + + def testConstructorSubClassWithParent(self): + self.assertEqual(bool(SubClassExtension(QObject())), True) + + def testConstructorInvalidParameter(self): + with self.assertRaises(TypeError): + SubClassExtension(str('')) + + def testConstructorAbstractClass(self): + with self.assertRaises(TypeError): + Extension() + + def testExtensionHasMethodsetup(self): + setup = getattr(SubClassExtension(), "setup", None) + self.assertEqual(bool(callable(setup)), True) + + +class SubClassExtension(Extension): + + def setup(self): + pass diff --git a/plugins/extensions/pykrita/tests/actionTests/filter_test.py b/plugins/extensions/pykrita/tests/actionTests/filter_test.py new file mode 100644 index 0000000000..e472aacd73 --- /dev/null +++ b/plugins/extensions/pykrita/tests/actionTests/filter_test.py @@ -0,0 +1,31 @@ +import unittest +from krita import Filter, InfoObject +from PyQt5.QtCore import QObject + + +class TestFilter(unittest.TestCase): + + def setUp(self): + self._instance = Filter() + + def testConstructor(self): + self.assertTrue(Filter()) + + def testConstructorInvalidParameter(self): + with self.assertRaises(TypeError): + Filter(str('')) + + def testEqualOperator(self): + sameFilter = self._instance + self.assertTrue(sameFilter == self._instance) + + def testEmptyNameProperty(self): + self.assertFalse(self._instance.name()) + + # segmentation fault here, I neet to verify that. + def testConfigurationProperties(self): + pass + # infoObject = InfoObject() + # f = Filter() + # f.setConfiguration(infoObject) + # self.assertEqual(f.configuration(), infoObject) diff --git a/plugins/extensions/pykrita/tests/actionTests/infoobject_test.py b/plugins/extensions/pykrita/tests/actionTests/infoobject_test.py new file mode 100644 index 0000000000..71682dcc42 --- /dev/null +++ b/plugins/extensions/pykrita/tests/actionTests/infoobject_test.py @@ -0,0 +1,43 @@ +import unittest +from krita import InfoObject +from PyQt5.QtCore import QObject + + +class TestInfoObject(unittest.TestCase): + + def setUp(self): + self._instance = InfoObject() + + def testConstructor(self): + self.assertTrue(InfoObject()) + + def testConstructorWithParent(self): + self.assertTrue(InfoObject(QObject())) + + def testConstructorInvalidParameter(self): + with self.assertRaises(TypeError): + InfoObject(str('')) + + def testEqualOperator(self): + sameInfoObject = self._instance + self.assertTrue(sameInfoObject == self._instance) + + def testInequalityOperator(self): + newInfoObject = InfoObject() + self.assertTrue(newInfoObject != self._instance) + + def testPropertiesAcessorsOneProperty(self): + self._instance.setProperties({"test": "test"}) + self.assertEqual(self._instance.properties(), {"test": "test"}) + + def testPropertiesAcessorsSetProperties(self): + self._instance.setProperties({"test": "test", "test1": 1}) + self.assertEqual(self._instance.properties(), {"test": "test", "test1": 1}) + + def testPropertySlotsString(self): + self._instance.setProperty("key", "value") + self.assertEqual(self._instance.property("key"), "value") + + def testPropertySlotsInvalidKey(self): + self._instance.setProperty("key", "value") + self.assertEqual(self._instance.property("keys"), None) diff --git a/plugins/extensions/pykrita/tests/basetest.py b/plugins/extensions/pykrita/tests/basetest.py new file mode 100644 index 0000000000..296db56c6d --- /dev/null +++ b/plugins/extensions/pykrita/tests/basetest.py @@ -0,0 +1,8 @@ +import unittest +import sys + +inst_dir = sys.argv[2] +source_dir = sys.argv[3] +sys.path.insert(0, str("{0}/share/krita/pykrita/PyKrita").format(inst_dir)) + +from krita import * diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp index e6a3b7d988..5879df7a11 100644 --- a/plugins/extensions/qmic/QMic.cpp +++ b/plugins/extensions/qmic/QMic.cpp @@ -1,494 +1,498 @@ /* * 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 "QMic.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 "kis_input_output_mapper.h" #include "kis_qmic_simple_convertor.h" #include "kis_import_qmic_processing_visitor.h" #include #include "kis_qmic_applicator.h" #include "kis_qmic_progress_manager.h" static const char ack[] = "ack"; K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin();) QMic::QMic(QObject *parent, const QVariantList &) : KisViewPlugin(parent) , m_gmicApplicator(0) , m_progressManager(0) { KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PluginSettingsFactory* settingsFactory = new PluginSettingsFactory(); preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory); m_qmicAction = createAction("QMic"); m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic())); m_againAction = createAction("QMicAgain"); m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_againAction->setEnabled(false); connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain())); m_gmicApplicator = new KisQmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString))); } QMic::~QMic() { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key(); memorySegment->detach(); } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); if (m_pluginProcess) { m_pluginProcess->close(); } delete m_gmicApplicator; delete m_progressManager; delete m_localServer; } void QMic::slotQMicAgain() { slotQMic(true); } void QMic::slotQMic(bool again) { m_qmicAction->setEnabled(false); m_againAction->setEnabled(false); if (m_pluginProcess) { qDebug() << "Plugin is already started" << m_pluginProcess->state(); return; } delete m_progressManager; m_progressManager = new KisQmicProgressManager(m_view); connect(m_progressManager, SIGNAL(sigProgress()), this, SLOT(slotUpdateProgress())); // find the krita-gmic-qt plugin KisConfig cfg; QString pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { KoDialog dlg; dlg.setWindowTitle(i18nc("@title:Window", "Krita")); QWidget *w = new QWidget(&dlg); dlg.setMainWidget(w); QVBoxLayout *l = new QVBoxLayout(w); l->addWidget(new PluginSettings(w)); dlg.setButtons(KoDialog::Ok); dlg.exec(); pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { return; } } m_key = QUuid::createUuid().toString(); m_localServer = new QLocalServer(); m_localServer->listen(m_key); connect(m_localServer, SIGNAL(newConnection()), SLOT(connected())); m_pluginProcess = new QProcess(this); m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels); connect(m_pluginProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus))); connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState))); m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString::null)); bool r = m_pluginProcess->waitForStarted(); while (m_pluginProcess->waitForFinished(10)) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } qDebug() << "Plugin started" << r << m_pluginProcess->state(); } void QMic::connected() { qDebug() << "connected"; QLocalSocket *socket = m_localServer->nextPendingConnection(); if (!socket) { return; } while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) { // stale request return; } socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray msg; quint32 remaining; ds >> remaining; msg.resize(remaining); int got = 0; char* uMsgBuf = msg.data(); + // FIXME: Should use read transaction for Qt >= 5.7: + // https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning() << "Message reception failed" << socket->errorString(); delete socket; m_localServer->close(); delete m_localServer; m_localServer = 0; return; } QString message = QString::fromUtf8(msg); qDebug() << "Received" << message; // Check the message: we can get three different ones QMultiMap messageMap; Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) { QList kv = line.split('=', QString::SkipEmptyParts); if (kv.size() == 2) { messageMap.insert(kv[0], kv[1]); } else { qWarning() << "line" << line << "is invalid."; } } if (!messageMap.contains("command")) { qWarning() << "Message did not contain a command"; return; } int mode = 0; if (messageMap.contains("mode")) { mode = messageMap.values("mode").first().toInt(); } QByteArray ba; if (messageMap.values("command").first() == "gmic_qt_get_image_size") { KisSelectionSP selection = m_view->image()->globalSelection(); if (selection) { QRect selectionRect = selection->selectedExactRect(); ba = QByteArray::number(selectionRect.width()) + "," + QByteArray::number(selectionRect.height()); } else { ba = QByteArray::number(m_view->image()->width()) + "," + QByteArray::number(m_view->image()->height()); } } else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") { // Parse the message, create the shared memory segments, and create a new message to send back and waid for ack QRectF cropRect(0.0, 0.0, 1.0, 1.0); if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) { qWarning() << "gmic-qt didn't send a croprect or not a valid croprect"; } else { QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts); cropRect.setX(cr[0].toFloat()); cropRect.setY(cr[1].toFloat()); cropRect.setWidth(cr[2].toFloat()); cropRect.setHeight(cr[3].toFloat()); } if (!prepareCroppedImages(&ba, cropRect, mode)) { qWarning() << "Failed to prepare images for gmic-qt"; } } else if (messageMap.values("command").first() == "gmic_qt_output_images") { // Parse the message. read the shared memory segments, fix up the current image and send an ack qDebug() << "gmic_qt_output_images"; QStringList layers = messageMap.values("layer"); m_outputMode = (OutputMode)mode; if (m_outputMode != IN_PLACE) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Sorry, this output mode is not implemented yet.")); m_outputMode = IN_PLACE; } slotStartApplicator(layers); } else if (messageMap.values("command").first() == "gmic_qt_detach") { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key() << memorySegment->isAttached(); if (memorySegment->isAttached()) { if (!memorySegment->detach()) { qDebug() << "\t" << memorySegment->error() << memorySegment->errorString(); } } } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); } else { qWarning() << "Received unknown command" << messageMap.values("command"); } qDebug() << "Sending" << QString::fromUtf8(ba); + // HACK: Make sure QDataStream does not refuse to write! + // Proper fix: Change the above read to use read transaction + ds.resetStatus(); ds.writeBytes(ba.constData(), ba.length()); // Flush the socket because we might not return to the event loop! if (!socket->waitForBytesWritten(2000)) { qWarning() << "Failed to write response:" << socket->error(); } // Wait for the ack bool r = true; r &= socket->waitForReadyRead(2000); // wait for ack r &= (socket->read(qstrlen(ack)) == ack); if (!socket->waitForDisconnected(2000)) { qWarning() << "Remote not disconnected:" << socket->error(); // Wait again socket->disconnectFromServer(); if (socket->waitForDisconnected(2000)) { qWarning() << "Disconnect timed out:" << socket->error(); } } } void QMic::pluginStateChanged(QProcess::ProcessState state) { qDebug() << "stateChanged" << state; } void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus) { qDebug() << "pluginFinished" << exitCode << exitStatus; delete m_pluginProcess; m_pluginProcess = 0; delete m_localServer; m_localServer = 0; delete m_progressManager; m_progressManager = 0; m_qmicAction->setEnabled(true); m_againAction->setEnabled(true); } void QMic::slotUpdateProgress() { if (!m_gmicApplicator) { qWarning() << "G'Mic applicator already deleted!"; return; } qDebug() << "slotUpdateProgress" << m_gmicApplicator->getProgress(); m_progressManager->updateProgress(m_gmicApplicator->getProgress()); } void QMic::slotStartProgressReporting() { qDebug() << "slotStartProgressReporting();"; if (m_progressManager->inProgress()) { m_progressManager->finishProgress(); } m_progressManager->initProgress(); } void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg) { qDebug() << "slotGmicFinished();" << successfully << milliseconds << msg; if (successfully) { m_gmicApplicator->finish(); } else { m_gmicApplicator->cancel(); QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg); } } void QMic::slotStartApplicator(QStringList gmicImages) { qDebug() << "slotStartApplicator();" << gmicImages; // Create a vector of gmic images QVector *> images; Q_FOREACH(const QString &image, gmicImages) { QStringList parts = image.split(',', QString::SkipEmptyParts); Q_ASSERT(parts.size() == 4); QString key = parts[0]; QString layerName = QByteArray::fromHex(parts[1].toLatin1()); int spectrum = parts[2].toInt(); int width = parts[3].toInt(); int height = parts[4].toInt(); qDebug() << key << layerName << width << height; QSharedMemory m(key); if (!m.attach(QSharedMemory::ReadOnly)) { qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString(); } if (m.isAttached()) { if (!m.lock()) { qDebug() << "Could not lock memeory segment" << m.error() << m.errorString(); } qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data(); gmic_image *gimg = new gmic_image(); gimg->assign(width, height, 1, spectrum); gimg->name = layerName; gimg->_data = new float[width * height * spectrum * sizeof(float)]; qDebug() << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float)); qDebug() << "created gmic image" << gimg->name << gimg->_width << gimg->_height; if (!m.unlock()) { qDebug() << "Could not unlock memeory segment" << m.error() << m.errorString(); } if (!m.detach()) { qDebug() << "Could not detach from memeory segment" << m.error() << m.errorString(); } images.append(gimg); } } qDebug() << "Got" << images.size() << "gmic images"; // Start the applicator KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = m_view->image()->root(); KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP layers = mapper.inputNodes(m_inputMode); m_gmicApplicator->setProperties(m_view->image(), rootNode, images, actionName, layers); - slotStartProgressReporting(); m_gmicApplicator->preview(); m_gmicApplicator->finish(); } bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode) { m_view->image()->lock(); m_inputMode = (InputLayerMode)inputMode; qDebug() << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP nodes = mapper.inputNodes(m_inputMode); if (nodes->isEmpty()) { m_view->image()->unlock(); return false; } for (int i = 0; i < nodes->size(); ++i) { KisNodeSP node = nodes->at(i); if (node->paintDevice()) { QRect cropRect; KisSelectionSP selection = m_view->image()->globalSelection(); if (selection) { cropRect = selection->selectedExactRect(); } else { cropRect = m_view->image()->bounds(); } qDebug() << "Converting node" << node->name() << cropRect; const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc); const QRect resultRect = mappedRect.toAlignedRect(); QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString())); m_sharedMemorySegments.append(m); if (!m->create(resultRect.width() * resultRect.height() * 4 * sizeof(float))) { //buf.size())) { qWarning() << "Could not create shared memory segment" << m->error() << m->errorString(); return false; } m->lock(); gmic_image img; img.assign(resultRect.width(), resultRect.height(), 1, 4); img._data = reinterpret_cast(m->data()); KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect); message->append(m->key().toUtf8()); m->unlock(); qDebug() << "size" << m->size(); message->append(","); message->append(node->name().toUtf8().toHex()); message->append(","); message->append(QByteArray::number(resultRect.width())); message->append(","); message->append(QByteArray::number(resultRect.height())); message->append("\n"); } } qDebug() << QString::fromUtf8(*message); m_view->image()->unlock(); return true; } #include "QMic.moc" diff --git a/plugins/extensions/qmic/kis_qmic_progress_manager.cpp b/plugins/extensions/qmic/kis_qmic_progress_manager.cpp index 43b46c0630..7af5c2573f 100644 --- a/plugins/extensions/qmic/kis_qmic_progress_manager.cpp +++ b/plugins/extensions/qmic/kis_qmic_progress_manager.cpp @@ -1,86 +1,87 @@ /* * Copyright (c) 2014 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_qmic_progress_manager.h" #include #include +#include static const int UPDATE_PROGRESS_TIMEOUT = 500; KisQmicProgressManager::KisQmicProgressManager(KisViewManager* viewManager) : m_progressPulseRequest(0) { - m_progressUpdater = viewManager->createProgressUpdater(KoProgressUpdater::Unthreaded); + m_progressUpdater = new KoProgressUpdater(viewManager->createUnthreadedUpdater("")); m_progressTimer.setInterval(UPDATE_PROGRESS_TIMEOUT); connect(&m_progressTimer, SIGNAL(timeout()), this, SIGNAL(sigProgress())); } KisQmicProgressManager::~KisQmicProgressManager() { QApplication::restoreOverrideCursor(); delete m_progressUpdater; } void KisQmicProgressManager::initProgress() { m_progressTimer.start(); QApplication::setOverrideCursor(Qt::WaitCursor); m_updater = m_progressUpdater->startSubtask(); m_progressPulseRequest = 0; } void KisQmicProgressManager::updateProgress(float progress) { int currentProgress = 0.0; if (progress >= 0.0) { if (m_progressPulseRequest > 0) { m_progressUpdater->start(100); m_updater = m_progressUpdater->startSubtask(); m_progressPulseRequest = 0; } currentProgress = (int)progress; } else { // pulse m_progressPulseRequest++; if (m_updater->progress() >= 90) { m_progressUpdater->start(100); m_updater = m_progressUpdater->startSubtask(); } currentProgress = (m_progressPulseRequest % 10) * 10; } dbgPlugins << "Current progress : " << currentProgress << " vs " << progress; m_updater->setProgress(currentProgress); } void KisQmicProgressManager::finishProgress() { m_progressTimer.stop(); QApplication::restoreOverrideCursor(); m_updater->setProgress(100); } bool KisQmicProgressManager::inProgress() { return m_progressTimer.isActive(); } diff --git a/plugins/extensions/separate_channels/kis_channel_separator.cc b/plugins/extensions/separate_channels/kis_channel_separator.cc index 0748064cd1..32859b9ead 100644 --- a/plugins/extensions/separate_channels/kis_channel_separator.cc +++ b/plugins/extensions/separate_channels/kis_channel_separator.cc @@ -1,273 +1,272 @@ /* * This file is part of Krita * * Copyright (c) 2005 Michael Thaler * * ported from Gimp, Copyright (C) 1997 Eiichi Takamori * original pixelize.c for GIMP 0.54 by Tracy Scott * * 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_channel_separator.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_iterator_ng.h" #include #include #include #include #include #include KisChannelSeparator::KisChannelSeparator(KisViewManager * view) : m_view(view) { } void KisChannelSeparator::separate(KoUpdater * progressUpdater, enumSepAlphaOptions alphaOps, enumSepSource sourceOps, enumSepOutput outputOps, bool downscale, bool toColor) { KisImageSP image = m_view->image(); if (!image) return; KisPaintDeviceSP src; // Use the flattened image, if required switch (sourceOps) { case ALL_LAYERS: // the content will be locked later src = image->projection(); break; case CURRENT_LAYER: src = m_view->activeDevice(); break; default: break; } if (!src) return; progressUpdater->setProgress(1); const KoColorSpace * dstCs = 0; quint32 numberOfChannels = src->channelCount(); const KoColorSpace * srcCs = src->colorSpace(); QList channels = srcCs->channels(); vKisPaintDeviceSP layers; QList::const_iterator begin = channels.constBegin(); QList::const_iterator end = channels.constEnd(); QRect rect = src->exactBounds(); image->lock(); int i = 0; for (QList::const_iterator it = begin; it != end; ++it) { KoChannelInfo * ch = (*it); if (ch->channelType() == KoChannelInfo::ALPHA && alphaOps != CREATE_ALPHA_SEPARATION) { continue; } qint32 channelSize = ch->size(); qint32 channelPos = ch->pos(); qint32 destSize = 1; KisPaintDeviceSP dev; if (toColor) { // We don't downscale if we separate to color channels dev = new KisPaintDevice(srcCs); } else { if (channelSize == 1 || downscale) { dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0)); } else { dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), 0)); destSize = 2; } } dstCs = dev->colorSpace(); layers.push_back(dev); KisHLineConstIteratorSP srcIt = src->createHLineConstIteratorNG(rect.x(), rect.y(), rect.width()); KisHLineIteratorSP dstIt = dev->createHLineIteratorNG(rect.x(), rect.y(), rect.width()); for (qint32 row = 0; row < rect.height(); ++row) { do { if (toColor) { dstCs->singleChannelPixel(dstIt->rawData(), srcIt->oldRawData(), channelPos); if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { //dstCs->setAlpha(dstIt->rawData(), srcIt->oldRawData()[srcAlphaPos], 1); dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else { // To grayscale // Decide whether we need downscaling if (channelSize == 1 && destSize == 1) { // Both 8-bit channels dstIt->rawData()[0] = srcIt->oldRawData()[channelPos]; if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else if (channelSize == 2 && destSize == 2) { // Both 16-bit dstIt->rawData()[0] = srcIt->oldRawData()[channelPos]; dstIt->rawData()[1] = srcIt->oldRawData()[channelPos + 1]; if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else if (channelSize != 1 && destSize == 1) { // Downscale memset(dstIt->rawData(), srcCs->scaleToU8(srcIt->oldRawData(), channelPos), 1); // XXX: Do alpha dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } else if (channelSize != 2 && destSize == 2) { // Upscale dstIt->rawData()[0] = srcCs->scaleToU8(srcIt->oldRawData(), channelPos); // XXX: Do alpha dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } } while (dstIt->nextPixel() && srcIt->nextPixel()); dstIt->nextRow(); srcIt->nextRow(); } ++i; progressUpdater->setProgress((i * 100) / numberOfChannels); if (progressUpdater->interrupted()) { break; } } vKisPaintDeviceSP_it deviceIt = layers.begin(); progressUpdater->setProgress(100); if (!progressUpdater->interrupted()) { KisUndoAdapter * undo = image->undoAdapter(); if (outputOps == TO_LAYERS) { undo->beginMacro(kundo2_i18n("Separate Image")); } // Flatten the image if required switch (sourceOps) { case(ALL_LAYERS): image->flatten(); break; default: break; } KisNodeCommandsAdapter adapter(m_view); for (QList::const_iterator it = begin; it != end; ++it) { KoChannelInfo * ch = (*it); if (ch->channelType() == KoChannelInfo::ALPHA && alphaOps != CREATE_ALPHA_SEPARATION) { // Don't make an separate separation of the alpha channel if the user didn't ask for it. continue; } if (outputOps == TO_LAYERS) { KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), ch->name(), OPACITY_OPAQUE_U8, *deviceIt)); adapter.addNode(l.data(), image->rootLayer(), 0); } else { KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export Layer") + '(' + ch->name() + ')'); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export)); QUrl url = QUrl::fromUserInput(dialog.filename()); if (url.isEmpty()) return; - QString mimefilter = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); + const QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), ch->name(), OPACITY_OPAQUE_U8, *deviceIt)); QRect r = l->exactBounds(); KisDocument *d = KisPart::instance()->createDocument(); KisImageWSP dst = KisImageWSP(new KisImage(d->createUndoStore(), r.width(), r.height(), (*deviceIt)->colorSpace(), l->name())); d->setCurrentImage(dst); dst->addNode(l->clone().data(), dst->rootLayer()); - d->setOutputMimeType(mimefilter.toLatin1()); - d->exportDocument(url); + d->exportDocumentSync(url, mimeType.toLatin1()); delete d; } ++deviceIt; } if (outputOps == TO_LAYERS) { undo->endMacro(); } image->unlock(); image->setModified(); } } diff --git a/plugins/extensions/separate_channels/kis_separate_channels_plugin.cc b/plugins/extensions/separate_channels/kis_separate_channels_plugin.cc index e09d46b578..ece1e7c064 100644 --- a/plugins/extensions/separate_channels/kis_separate_channels_plugin.cc +++ b/plugins/extensions/separate_channels/kis_separate_channels_plugin.cc @@ -1,103 +1,99 @@ /* * This file is part of the KDE project * * Copyright (c) 2005 Michael Thaler * * 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_separate_channels_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_channel_separator.h" #include "dlg_separate.h" K_PLUGIN_FACTORY_WITH_JSON(KisSeparateChannelsPluginFactory, "kritaseparatechannels.json", registerPlugin();) KisSeparateChannelsPlugin::KisSeparateChannelsPlugin(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("separate"); connect(action, SIGNAL(triggered(bool)), SLOT(slotSeparate())); } KisSeparateChannelsPlugin::~KisSeparateChannelsPlugin() { } void KisSeparateChannelsPlugin::slotSeparate() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP l = m_view->nodeManager()->activeLayer(); if (!l) return; KisPaintDeviceSP dev = l->paintDevice(); if (!dev) return; DlgSeparate * dlgSeparate = new DlgSeparate(dev->colorSpace()->name(), image->colorSpace()->name(), m_view->mainWindow(), "Separate"); Q_CHECK_PTR(dlgSeparate); dlgSeparate->setCaption(i18n("Separate Image")); // If we're 8-bits, disable the downscale option if (dev->pixelSize() == dev->channelCount()) { dlgSeparate->enableDownscale(false); } if (dlgSeparate->exec() == QDialog::Accepted) { QApplication::setOverrideCursor(Qt::BusyCursor); - KoProgressUpdater* pu = m_view->createProgressUpdater(KoProgressUpdater::Unthreaded); - pu->start(100, i18n("Separate Image")); - QPointer u = pu->startSubtask(); KisChannelSeparator separator(m_view); - separator.separate(u, + separator.separate(m_view->createUnthreadedUpdater(i18n("Separate Image")), dlgSeparate->getAlphaOptions(), dlgSeparate->getSource(), dlgSeparate->getOutput(), dlgSeparate->getDownscale(), dlgSeparate->getToColor()); - pu->deleteLater(); QApplication::restoreOverrideCursor(); } delete dlgSeparate; } #include "kis_separate_channels_plugin.moc" diff --git a/plugins/extensions/waveletdecompose/waveletdecompose.cpp b/plugins/extensions/waveletdecompose/waveletdecompose.cpp index b8587afa08..ce52f85e66 100644 --- a/plugins/extensions/waveletdecompose/waveletdecompose.cpp +++ b/plugins/extensions/waveletdecompose/waveletdecompose.cpp @@ -1,161 +1,158 @@ /* * Copyright (C) 2016 Miroslav Talasek * * 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 "waveletdecompose.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_waveletdecompose.h" #include "kis_node_manager.h" #include "kis_node_commands_adapter.h" #include "kis_undo_adapter.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(WaveletDecomposeFactory, "kritawaveletdecompose.json", registerPlugin();) WaveletDecompose::WaveletDecompose(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("waveletdecompose"); connect(action, SIGNAL(triggered()), this, SLOT(slotWaveletDecompose())); } WaveletDecompose::~WaveletDecompose() { } void WaveletDecompose::slotWaveletDecompose() { DlgWaveletDecompose dlg(m_view->mainWindow(), "WaveletDecompose"); if (dlg.exec() == QDialog::Accepted) { QApplication::setOverrideCursor(Qt::WaitCursor); - KoProgressUpdater* pu = m_view->createProgressUpdater(KoProgressUpdater::Unthreaded); - pu->start(100, i18n("Wavelet Decompose")); - QPointer updater = pu->startSubtask(); - updater->setProgress(0); + QPointer updater = m_view->createUnthreadedUpdater(i18n("Wavelet Decompose")); KisImageSP image = m_view->image(); if (!image) return; if (!m_view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP projection = new KisPaintDevice(*(image->projection()), false, 0); if (!projection) return; const KoColorSpace *cs = projection->colorSpace(); const KoCompositeOp* op = cs->compositeOp(COMPOSITE_GRAIN_EXTRACT); int scales = dlg.scales(); QList results; const QBitArray flags(0); QRect rc = image->bounds(); KisPaintDeviceSP original = projection; //main loop for(int level = 0; level < scales; ++level){ //copy original KisPaintDeviceSP blur = new KisPaintDevice(*original, false, 0); //blur it KisWaveletKernel::applyWavelet(blur, rc, 1 << level, 1 << level, flags, 0); //do grain extract blur from original KisPainter painter(original); painter.setCompositeOp(op); painter.bitBlt(0, 0, blur, 0, 0, rc.width(), rc.height()); painter.end(); //original is new scale and blur is new original results << original; original = blur; updater->setProgress((level * 100) / scales); } //add new layers KisUndoAdapter *undo = image->undoAdapter(); undo->beginMacro(kundo2_i18n("Wavelet decompose")); KisNodeCommandsAdapter adapter(m_view); KisGroupLayerSP baseGroup = image->rootLayer(); //add layer goup KisGroupLayerSP grp = new KisGroupLayer(image, i18n("Wavelet decompose"), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, baseGroup->lastChild()); baseGroup = grp; //add scales int i = 1; const KoCompositeOp* op2 = cs->compositeOp(COMPOSITE_GRAIN_MERGE); Q_FOREACH (const KisPaintDeviceSP &l, results) { KisPaintLayerSP paintLayer = new KisPaintLayer(image, QStringLiteral("Scale %1").arg(i), OPACITY_OPAQUE_U8, l); adapter.addNode(paintLayer, baseGroup, 0); adapter.setCompositeOp(paintLayer, op2); ++i; } //add residual KisPaintLayerSP paintLayer = new KisPaintLayer(image, "Residual", OPACITY_OPAQUE_U8, original); adapter.addNode(paintLayer, baseGroup, 0); undo->endMacro(); updater->setProgress(100); image->unlock(); image->setModified(); } QApplication::restoreOverrideCursor(); } #include "waveletdecompose.moc" diff --git a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp index 66e11a2755..f197ce3eb9 100644 --- a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp +++ b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp @@ -1,190 +1,207 @@ /* * 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; version 2 * 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 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_hsv_adjustment_filter.h" #include #include #include #include #include namespace { struct SliderConfig { QString m_text; int m_minimum; int m_maximum; inline void apply(QSpinBox* spinBox, QSlider* slider, QLabel* label) const { label->setText(m_text); slider->setMinimum(m_minimum); slider->setMaximum(m_maximum); spinBox->setMinimum(m_minimum); spinBox->setMaximum(m_maximum); int sliderValue = slider->value(); if (sliderValue < m_minimum || sliderValue > m_maximum) { slider->setValue((m_minimum + m_maximum) / 2); } } inline double normalize(int value) const { return (double)value / (double)m_maximum; } + + inline void resetSlider( QSlider* slider) const + { + slider->setValue(0); + } + }; struct WidgetSlidersConfig { SliderConfig m_sliders[3]; }; #define PERCENT_FIELD_REL(x) {x, -100, 100} #define PERCENT_FIELD_ABS(x) {x, 0, 100} #define DEGREES_FIELD_REL(x) {x, -180, 180} #define DEGREES_FIELD_ABS(x) {x, 0, 360} #define HSX_CONFIGS(x) { \ { {DEGREES_FIELD_REL(i18n("Hue:")), PERCENT_FIELD_REL(i18n("Saturation:")), PERCENT_FIELD_REL(x)} }, \ { {DEGREES_FIELD_ABS(i18n("Hue:")), PERCENT_FIELD_ABS(i18n("Saturation:")), PERCENT_FIELD_REL(x)} } \ } const WidgetSlidersConfig WIDGET_CONFIGS[][2] = { // Hue/Saturation/Value HSX_CONFIGS(i18n("Value:")), // Hue/Saturation/Lightness HSX_CONFIGS(i18n("Lightness:")), // Hue/Saturation/Intensity HSX_CONFIGS(i18n("Intensity:")), // Hue/Saturation/Luminosity HSX_CONFIGS(i18n("Luma:")), // Blue Chroma/Red Chroma/Luma {{ {PERCENT_FIELD_REL(i18n("Yellow-Blue:")), PERCENT_FIELD_REL(i18n("Green-Red:")), PERCENT_FIELD_REL(i18n("Luma:"))} }, { {PERCENT_FIELD_ABS(i18n("Yellow-Blue:")), PERCENT_FIELD_ABS(i18n("Green-Red:")), PERCENT_FIELD_REL(i18n("Luma:"))} }} }; inline const WidgetSlidersConfig& getCurrentWidgetConfig(int type, bool colorize) { return WIDGET_CONFIGS[type][colorize ? 1 : 0]; } } KisHSVAdjustmentFilter::KisHSVAdjustmentFilter() : KisColorTransformationFilter(id(), categoryAdjust(), i18n("&HSV Adjustment...")) { setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U)); setSupportsPainting(true); } KisConfigWidget * KisHSVAdjustmentFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const { Q_UNUSED(dev); return new KisHSVConfigWidget(parent); } KoColorTransformation* KisHSVAdjustmentFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { QHash params; if (config) { int type = config->getInt("type", 1); bool colorize = config->getBool("colorize", false); const WidgetSlidersConfig& widgetConfig = getCurrentWidgetConfig(type, colorize); params["h"] = widgetConfig.m_sliders[0].normalize(config->getInt("h", 0)); params["s"] = widgetConfig.m_sliders[1].normalize(config->getInt("s", 0)); params["v"] = widgetConfig.m_sliders[2].normalize(config->getInt("v", 0)); params["type"] = type; params["colorize"] = colorize; params["lumaRed"] = cs->lumaCoefficients()[0]; params["lumaGreen"] = cs->lumaCoefficients()[1]; params["lumaBlue"] = cs->lumaCoefficients()[2]; } return cs->createColorTransformation("hsv_adjustment", params); } KisFilterConfigurationSP KisHSVAdjustmentFilter::factoryConfiguration() const { KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 1); config->setProperty("h", 0); config->setProperty("s", 0); config->setProperty("v", 0); config->setProperty("type", 1); config->setProperty("colorize", false); return config; } KisHSVConfigWidget::KisHSVConfigWidget(QWidget * parent, Qt::WFlags f) : KisConfigWidget(parent, f) { m_page = new Ui_WdgHSVAdjustment(); m_page->setupUi(this); connect(m_page->cmbType, SIGNAL(activated(int)), this, SLOT(configureSliderLimitsAndLabels())); connect(m_page->chkColorize, SIGNAL(toggled(bool)), this, SLOT(configureSliderLimitsAndLabels())); + connect(m_page->reset,SIGNAL(clicked(bool)),this,SLOT(resetFilter())); // connect horizontal sliders connect(m_page->hueSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->saturationSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->valueSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->hueSpinBox, SIGNAL(valueChanged(int)), m_page->hueSlider, SLOT(setValue(int))); connect(m_page->saturationSpinBox, SIGNAL(valueChanged(int)), m_page->saturationSlider, SLOT(setValue(int))); connect(m_page->valueSpinBox, SIGNAL(valueChanged(int)), m_page->valueSlider, SLOT(setValue(int))); connect(m_page->hueSlider, SIGNAL(valueChanged(int)), m_page->hueSpinBox, SLOT(setValue(int))); connect(m_page->saturationSlider, SIGNAL(valueChanged(int)), m_page->saturationSpinBox, SLOT(setValue(int))); connect(m_page->valueSlider, SIGNAL(valueChanged(int)), m_page->valueSpinBox, SLOT(setValue(int))); } KisHSVConfigWidget::~KisHSVConfigWidget() { delete m_page; } KisPropertiesConfigurationSP KisHSVConfigWidget::configuration() const { KisColorTransformationConfigurationSP c = new KisColorTransformationConfiguration(KisHSVAdjustmentFilter::id().id(), 0); c->setProperty("h", m_page->hueSlider->value()); c->setProperty("s", m_page->saturationSlider->value()); c->setProperty("v", m_page->valueSlider->value()); c->setProperty("type", m_page->cmbType->currentIndex()); c->setProperty("colorize", m_page->chkColorize->isChecked()); return c; } void KisHSVConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { m_page->cmbType->setCurrentIndex(config->getInt("type", 1)); m_page->chkColorize->setChecked(config->getBool("colorize", false)); m_page->hueSlider->setValue(config->getInt("h", 0)); m_page->saturationSlider->setValue(config->getInt("s", 0)); m_page->valueSlider->setValue(config->getInt("v", 0)); configureSliderLimitsAndLabels(); } void KisHSVConfigWidget::configureSliderLimitsAndLabels() { const WidgetSlidersConfig& widget = getCurrentWidgetConfig(m_page->cmbType->currentIndex(), m_page->chkColorize->isChecked()); widget.m_sliders[0].apply(m_page->hueSpinBox, m_page->hueSlider, m_page->label); widget.m_sliders[1].apply(m_page->saturationSpinBox, m_page->saturationSlider, m_page->label_2); widget.m_sliders[2].apply(m_page->valueSpinBox, m_page->valueSlider, m_page->label_3); emit sigConfigurationItemChanged(); } + +void KisHSVConfigWidget::resetFilter() +{ + const WidgetSlidersConfig& widget = getCurrentWidgetConfig(m_page->cmbType->currentIndex(), m_page->chkColorize->isChecked()); + + widget.m_sliders[0].resetSlider(m_page->hueSlider); + widget.m_sliders[1].resetSlider(m_page->saturationSlider); + widget.m_sliders[2].resetSlider(m_page->valueSlider); +} + diff --git a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h index ae27f37363..3afb7c14e2 100644 --- a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h +++ b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h @@ -1,77 +1,78 @@ /* * 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; version 2 * 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 library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_HSV_ADJUSTMENT_FILTER_H_ #define _KIS_HSV_ADJUSTMENT_FILTER_H_ #include #include "filter/kis_filter.h" #include "kis_config_widget.h" #include "ui_wdg_hsv_adjustment.h" #include "filter/kis_color_transformation_filter.h" class QWidget; class KoColorTransformation; /** * This class affect Intensity Y of the image */ class KisHSVAdjustmentFilter : public KisColorTransformationFilter { public: KisHSVAdjustmentFilter(); public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override; KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; static inline KoID id() { return KoID("hsvadjustment", i18n("HSV/HSL Adjustment")); } KisFilterConfigurationSP factoryConfiguration() const override; }; class KisHSVConfigWidget : public KisConfigWidget { Q_OBJECT public: KisHSVConfigWidget(QWidget * parent, Qt::WFlags f = 0); ~KisHSVConfigWidget() override; KisPropertiesConfigurationSP configuration() const override; void setConfiguration(const KisPropertiesConfigurationSP config) override; Ui_WdgHSVAdjustment * m_page; private Q_SLOTS: void configureSliderLimitsAndLabels(); + void resetFilter(); }; #endif diff --git a/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui b/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui index f9eaa7556b..d88926d9ba 100644 --- a/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui +++ b/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui @@ -1,247 +1,254 @@ WdgHSVAdjustment 0 0 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: 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 + + + + Reset + + + KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
cmbType chkColorize
diff --git a/plugins/impex/csv/csv_loader.cpp b/plugins/impex/csv/csv_loader.cpp index 4cc3f017a9..2816490476 100644 --- a/plugins/impex/csv/csv_loader.cpp +++ b/plugins/impex/csv/csv_loader.cpp @@ -1,486 +1,489 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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 "csv_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_read_line.h" #include "csv_layer_record.h" CSVLoader::CSVLoader(KisDocument *doc, bool batchMode) : m_image(0) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVLoader::~CSVLoader() { } KisImageBuilder_Result CSVLoader::decode(QIODevice *io, const QString &filename) { QString field; int idx; int frame = 0; QString projName; int width = 0; int height = 0; int frameCount = 1; float framerate = 24.0; float pixelRatio = 1.0; int projNameIdx = -1; int widthIdx = -1; int heightIdx = -1; int frameCountIdx = -1; int framerateIdx = -1; int pixelRatioIdx = -1; QVector layers; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); idx = filename.lastIndexOf(QRegExp("[\\/]")); QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator QString path = filename; if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); //according to the QT docs, the slash is a universal directory separator path.append(".frames/"); KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK; dbgFile << "pos:" << io->pos(); CSVReadLine readLine; QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setAutoSaveDelay(0); importDoc->setFileBatchMode(true); KisView *setView(0); if (!m_batchMode) { - //show the statusbar message even if no view - Q_FOREACH (KisView* view, KisPart::instance()->views()) { - if (view && view->document() == m_doc) { - setView = view; - break; - } - } - - if (!setView) { - QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); - if (sb) { - sb->showMessage(i18n("Loading CSV file...")); - } - } else { - emit m_doc->statusBarMessage(i18n("Loading CSV file...")); - } - emit m_doc->sigProgress(0); - connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); + // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() + +// //show the statusbar message even if no view +// Q_FOREACH (KisView* view, KisPart::instance()->views()) { +// if (view && view->document() == m_doc) { +// setView = view; +// break; +// } +// } + +// if (!setView) { +// QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); +// if (sb) { +// sb->showMessage(i18n("Loading CSV file...")); +// } +// } else { +// emit m_doc->statusBarMessage(i18n("Loading CSV file...")); +// } + +// emit m_doc->sigProgress(0); +// connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int step = 0; do { qApp->processEvents(); if (m_stop) { retval = KisImageBuilder_RESULT_CANCEL; break; } if ((idx = readLine.nextLine(io)) <= 0) { if ((idx < 0) ||(step < 5)) retval = KisImageBuilder_RESULT_FAILURE; break; } field = readLine.nextField(); //first field of the line if (field.isNull()) continue; //empty row switch (step) { case 0 : //skip first row step = 1; break; case 1 : //scene header names step = 2; for (idx = 0; !field.isNull(); idx++) { if (field == "Project Name") { projNameIdx = idx; } else if (field == "Width") { widthIdx = idx; } else if (field == "Height") { heightIdx = idx; } else if (field == "Frame Count") { frameCountIdx = idx; } else if (field == "Frame Rate") { framerateIdx = idx; } else if (field == "Pixel Aspect Ratio") { pixelRatioIdx = idx; } field= readLine.nextField(); } break; case 2 : //scene header values step= 3; for (idx= 0; !field.isNull(); idx++) { if (idx == projNameIdx) { projName = field; } else if (idx == widthIdx) { width = field.toInt(); } else if (idx == heightIdx) { height = field.toInt(); } else if (idx == frameCountIdx) { frameCount = field.toInt(); if (frameCount < 1) frameCount= 1; } else if (idx == framerateIdx) { framerate = field.toFloat(); } else if (idx == pixelRatioIdx) { pixelRatio = field.toFloat(); } field= readLine.nextField(); } if ((width < 1) || (height < 1)) { retval = KisImageBuilder_RESULT_FAILURE; break; } retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName); break; case 3 : //create level headers if (field[0] != '#') break; for (; !(field = readLine.nextField()).isNull(); ) { CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.append(layerRecord); } readLine.rewind(); field = readLine.nextField(); step = 4; //no break! case 4 : //level header if (field == "#Layers") { //layer name for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->name = field; break; } if (field == "#Density") { //layer opacity for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->density = field.toFloat(); break; } if (field == "#Blending") { //layer blending mode for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->blending = field; break; } if (field == "#Visible") { //layer visibility for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->visible = field.toInt(); break; } if (field == "#Folder") { //CSV 1.1 folder location for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->path = validPath(field, base); break; } if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; step = 5; //no break! case 5 : //frames if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) { CSVLayerRecord* layer = layers.at(idx); if (layer->last != field) { if (!m_batchMode) { - emit m_doc->sigProgress((frame * layers.size() + idx) * 100 / - (frameCount * layers.size())); + //emit m_doc->sigProgress((frame * layers.size() + idx) * 100 / + // (frameCount * layers.size())); } retval = setLayer(layer, importDoc.data(), path); layer->last = field; layer->frame = frame; } } frame++; break; } } while (retval == KisImageBuilder_RESULT_OK); //finish the layers if (retval == KisImageBuilder_RESULT_OK) { if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); if (frame > frameCount) frameCount = frame; animation->setFullClipRange(KisTimeRange::fromTime(0,frameCount - 1)); animation->setFramerate((int)framerate); } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord* layer = layers.at(idx); //empty layers without any pictures are dropped if ((layer->frame > 0) || !layer->last.isEmpty()) { retval = setLayer(layer, importDoc.data(), path); if (retval != KisImageBuilder_RESULT_OK) break; } } } if (m_image) { //insert the existing layers by the right order for (idx = layers.size() - 1; idx >= 0; idx--) { CSVLayerRecord* layer = layers.at(idx); if (layer->layer) { m_image->addNode(layer->layer, m_image->root()); } } m_image->unlock(); } qDeleteAll(layers); io->close(); if (!m_batchMode) { - disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); - emit m_doc->sigProgress(100); + // disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); + // emit m_doc->sigProgress(100); if (!setView) { QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); if (sb) { sb->clearMessage(); } } else { emit m_doc->clearStatusBarMessage(); } } QApplication::restoreOverrideCursor(); return retval; } QString CSVLoader::convertBlending(const QString &blending) { if (blending == "Color") return COMPOSITE_OVER; if (blending == "Behind") return COMPOSITE_BEHIND; if (blending == "Erase") return COMPOSITE_ERASE; // "Shade" if (blending == "Light") return COMPOSITE_LINEAR_LIGHT; if (blending == "Colorize") return COMPOSITE_COLORIZE; if (blending == "Hue") return COMPOSITE_HUE; if (blending == "Add") return COMPOSITE_ADD; if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT; if (blending == "Multiply") return COMPOSITE_MULT; if (blending == "Screen") return COMPOSITE_SCREEN; // "Replace" // "Subtitute" if (blending == "Difference") return COMPOSITE_DIFF; if (blending == "Divide") return COMPOSITE_DIVIDE; if (blending == "Overlay") return COMPOSITE_OVERLAY; if (blending == "Light2") return COMPOSITE_DODGE; if (blending == "Shade2") return COMPOSITE_BURN; if (blending == "HardLight") return COMPOSITE_HARD_LIGHT; if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP; if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT; if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE; if (blending == "Sub2") return COMPOSITE_SUBTRACT; if (blending == "Darken") return COMPOSITE_DARKEN; if (blending == "Lighten") return COMPOSITE_LIGHTEN; if (blending == "Saturation") return COMPOSITE_SATURATION; return COMPOSITE_OVER; } QString CSVLoader::validPath(const QString &path,const QString &base) { //replace Windows directory separators with the universal / QString tryPath= QString(path).replace(QString("\\"), QString("/")); int i = tryPath.lastIndexOf("/"); if (i == (tryPath.size() - 1)) tryPath= tryPath.left(i); //remove the ending separator if exists if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); QString scan(tryPath); i = -1; while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) { //avoid testing if the next level will be the default xxxx.layers folder if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue; tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a / if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); } return QString(); //NULL string } KisImageBuilder_Result CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path) { bool result = true; if (layer->channel == 0) { //create a new document layer float opacity = layer->density; if (opacity > 1.0) opacity = 1.0; else if (opacity < 0.0) opacity = 0.0; const KoColorSpace* cs = m_image->colorSpace(); const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name; KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName, (quint8)(opacity * OPACITY_OPAQUE_U8), cs); paintLayer->setCompositeOpId(convertBlending(layer->blending)); paintLayer->setVisible(layer->visible); paintLayer->enableAnimation(); layer->layer = paintLayer; layer->channel = qobject_cast (paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } if (!layer->last.isEmpty()) { //png image QString filename = layer->path.isNull() ? path : layer->path; filename.append(layer->last); result = importDoc->openUrl(QUrl::fromLocalFile(filename), - KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES); + KisDocument::DontAddToRecent); if (result) layer->channel->importFrame(layer->frame, importDoc->image()->projection(), 0); } else { //blank layer->channel->addKeyframe(layer->frame); } return (result) ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; } KisImageBuilder_Result CSVLoader::createNewImage(int width, int height, float ratio, const QString &name) { //the CSV is RGBA 8bits, sRGB if (!m_image) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0); if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name); if (!m_image) return KisImageBuilder_RESULT_FAILURE; m_image->setResolution(ratio, 1.0); m_image->lock(); } return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result CSVLoader::buildAnimation(QIODevice *io, const QString &filename) { return decode(io, filename); } KisImageSP CSVLoader::image() { return m_image; } void CSVLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/csv/csv_saver.cpp b/plugins/impex/csv/csv_saver.cpp index 9f289423d6..f72e8cf6ea 100644 --- a/plugins/impex/csv/csv_saver.cpp +++ b/plugins/impex/csv/csv_saver.cpp @@ -1,478 +1,478 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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 "csv_saver.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 "csv_layer_record.h" CSVSaver::CSVSaver(KisDocument *doc, bool batchMode) : m_image(doc->savingImage()) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVSaver::~CSVSaver() { } KisImageSP CSVSaver::image() { return m_image; } KisImageBuilder_Result CSVSaver::encode(QIODevice *io) { int idx; int start, end; KisNodeSP node; QByteArray ba; KisKeyframeSP keyframe; QVector layers; KisImageAnimationInterface *animation = m_image->animationInterface(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // XXX: Stream was unused? // //DataStream instead of TextStream for correct line endings // QDataStream stream(&f); //Using the original local path QString path = m_doc->localFilePath(); if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); else { // something is wrong: the local file name is not .csv! // trying the given (probably temporary) filename as well // XXX: unbreak this! if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); } path.append(".frames"); //create directory QDir dir(path); if (!dir.exists()) { dir.mkpath("."); } //according to the QT docs, the slash is a universal directory separator path.append("/"); node = m_image->rootLayer()->firstChild(); //TODO: correct handling of the layer tree. //for now, only top level paint layers are saved idx = 0; while (node) { if (node->inherits("KisLayer")) { KisLayer* paintLayer = qobject_cast(node.data()); CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.prepend(layerRecord); //reverse order! layerRecord->name = paintLayer->name(); layerRecord->name.replace(QRegExp("[\"\\r\\n]"), "_"); if (layerRecord->name.isEmpty()) layerRecord->name= QString("Unnamed-%1").arg(idx); layerRecord->visible = (paintLayer->visible()) ? 1 : 0; layerRecord->density = (float)(paintLayer->opacity()) / OPACITY_OPAQUE_U8; layerRecord->blending = convertToBlending(paintLayer->compositeOpId()); layerRecord->layer = paintLayer; layerRecord->channel = paintLayer->original()->keyframeChannel(); layerRecord->last = ""; layerRecord->frame = 0; idx++; } node = node->nextSibling(); } KisTimeRange range = animation->fullClipRange(); start = (range.isValid()) ? range.start() : 0; if (!range.isInfinite()) { end = range.end(); if (end < start) end = start; } else { //undefined length, searching for the last keyframe end = start; for (idx = 0; idx < layers.size(); idx++) { KisRasterKeyframeChannel *channel = layers.at(idx)->channel; if (channel) { keyframe = channel->lastKeyframe(); if ( (!keyframe.isNull()) && (keyframe->time() > end) ) end = keyframe->time(); } } } //create temporary doc for exporting QScopedPointer exportDoc(KisPart::instance()->createDocument()); createTempImage(exportDoc.data()); KisImageBuilder_Result retval= KisImageBuilder_RESULT_OK; if (!m_batchMode) { - emit m_doc->statusBarMessage(i18n("Saving CSV file...")); - emit m_doc->sigProgress(0); - connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); + // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() + //emit m_doc->statusBarMessage(i18n("Saving CSV file...")); + //emit m_doc->sigProgress(0); + //connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int frame = start; int step = 0; do { qApp->processEvents(); if (m_stop) { retval = KisImageBuilder_RESULT_CANCEL; break; } switch(step) { case 0 : //first row if (io->write("UTF-8, TVPaint, \"CSV 1.0\"\r\n") < 0) { retval = KisImageBuilder_RESULT_FAILURE; } break; case 1 : //scene header names if (io->write("Project Name, Width, Height, Frame Count, Layer Count, Frame Rate, Pixel Aspect Ratio, Field Mode\r\n") < 0) { retval = KisImageBuilder_RESULT_FAILURE; } break; case 2 : //scene header values ba = QString("\"%1\", ").arg(m_image->objectName()).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, %2, ").arg(m_image->width()).arg(m_image->height()).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, %2, ").arg(end - start + 1).arg(layers.size()).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } //the framerate is an integer here ba = QString("%1, ").arg((double)(animation->framerate()),0,'f',6).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } ba = QString("%1, Progressive\r\n").arg((double)(m_image->xRes() / m_image->yRes()),0,'f',6).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } break; case 3 : //layer header values if (io->write("#Layers") < 0) { //Layers retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->name).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 4 : if (io->write("\r\n#Density") < 0) { //Density retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg((double)(layers.at(idx)->density), 0, 'f', 6).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 5 : if (io->write("\r\n#Blending") < 0) { //Blending retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->blending).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 6 : if (io->write("\r\n#Visible") < 0) { //Visible retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg(layers.at(idx)->visible).toUtf8(); if (io->write(ba.data()) < 0) break; } if (idx < layers.size()) { retval = KisImageBuilder_RESULT_FAILURE; } break; default : //frames if (frame > end) { if (io->write("\r\n") < 0) retval = KisImageBuilder_RESULT_FAILURE; step = 8; break; } ba = QString("\r\n#%1").arg(frame, 5, 10, QChar('0')).toUtf8(); if (io->write(ba.data()) < 0) { retval = KisImageBuilder_RESULT_FAILURE; break; } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord *layer = layers.at(idx); KisRasterKeyframeChannel *channel = layer->channel; if (channel) { if (frame == start) { keyframe = channel->activeKeyframeAt(frame); } else { keyframe = channel->keyframeAt(frame); } } else { keyframe.clear(); // without animation } if ( !keyframe.isNull() || (frame == start) ) { if (!m_batchMode) { - emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 / - ((end - start) * layers.size())); + //emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 / + // ((end - start) * layers.size())); } retval = getLayer(layer, exportDoc.data(), keyframe, path, frame, idx); if (retval != KisImageBuilder_RESULT_OK) break; } ba = QString(", \"%1\"").arg(layer->last).toUtf8(); if (io->write(ba.data()) < 0) break; } if (idx < layers.size()) retval = KisImageBuilder_RESULT_FAILURE; frame++; step = 6; //keep step here break; } step++; } while((retval == KisImageBuilder_RESULT_OK) && (step < 8)); qDeleteAll(layers); // io->close(); it seems this is not required anymore if (!m_batchMode) { - disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); - emit m_doc->sigProgress(100); - emit m_doc->clearStatusBarMessage(); + //disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); + //emit m_doc->sigProgress(100); + //emit m_doc->clearStatusBarMessage(); } QApplication::restoreOverrideCursor(); return retval; } QString CSVSaver::convertToBlending(const QString &opid) { if (opid == COMPOSITE_OVER) return "Color"; if (opid == COMPOSITE_BEHIND) return "Behind"; if (opid == COMPOSITE_ERASE) return "Erase"; // "Shade" if (opid == COMPOSITE_LINEAR_LIGHT) return "Light"; if (opid == COMPOSITE_COLORIZE) return "Colorize"; if (opid == COMPOSITE_HUE) return "Hue"; if ((opid == COMPOSITE_ADD) || (opid == COMPOSITE_LINEAR_DODGE)) return "Add"; if (opid == COMPOSITE_INVERSE_SUBTRACT) return "Sub"; if (opid == COMPOSITE_MULT) return "Multiply"; if (opid == COMPOSITE_SCREEN) return "Screen"; // "Replace" // "Subtitute" if (opid == COMPOSITE_DIFF) return "Difference"; if (opid == COMPOSITE_DIVIDE) return "Divide"; if (opid == COMPOSITE_OVERLAY) return "Overlay"; if (opid == COMPOSITE_DODGE) return "Light2"; if (opid == COMPOSITE_BURN) return "Shade2"; if (opid == COMPOSITE_HARD_LIGHT) return "HardLight"; if ((opid == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) || (opid == COMPOSITE_SOFT_LIGHT_SVG)) return "SoftLight"; if (opid == COMPOSITE_GRAIN_EXTRACT) return "GrainExtract"; if (opid == COMPOSITE_GRAIN_MERGE) return "GrainMerge"; if (opid == COMPOSITE_SUBTRACT) return "Sub2"; if (opid == COMPOSITE_DARKEN) return "Darken"; if (opid == COMPOSITE_LIGHTEN) return "Lighten"; if (opid == COMPOSITE_SATURATION) return "Saturation"; return "Color"; } KisImageBuilder_Result CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx) { //render to the temp layer KisImageSP image = exportDoc->savingImage(); if (!image) image= exportDoc->image(); KisPaintDeviceSP device = image->rootLayer()->firstChild()->projection(); if (!keyframe.isNull()) { layer->channel->fetchFrame(keyframe, device); } else { device->makeCloneFrom(layer->layer->projection(),image->bounds()); // without animation } QRect bounds = device->exactBounds(); if (bounds.isEmpty()) { layer->last = ""; //empty frame return KisImageBuilder_RESULT_OK; } layer->last = QString("frame%1-%2.png").arg(idx + 1,5,10,QChar('0')).arg(frame,5,10,QChar('0')); QString filename = path; filename.append(layer->last); //save to PNG KisSequentialConstIterator it(device, image->bounds()); const KoColorSpace* cs = device->colorSpace(); bool isThereAlpha = false; do { if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) { isThereAlpha = true; break; } } while (it.nextPixel()); if (!KisPNGConverter::isColorSpaceSupported(cs)) { device = new KisPaintDevice(*device.data()); KUndo2Command *cmd= device->convertTo(KoColorSpaceRegistry::instance()->rgb8()); delete cmd; } KisPNGOptions options; options.alpha = isThereAlpha; options.interlace = false; options.compression = 8; options.tryToSaveAsIndexed = false; options.transparencyFillColor = QColor(0,0,0); options.saveSRGBProfile = true; //TVPaint can use only sRGB options.forceSRGB = false; KisPNGConverter kpc(exportDoc); KisImageBuilder_Result result = kpc.buildFile(filename, image->bounds(), image->xRes(), image->yRes(), device, image->beginAnnotations(), image->endAnnotations(), options, (KisMetaData::Store* )0 ); return result; } void CSVSaver::createTempImage(KisDocument* exportDoc) { exportDoc->setAutoSaveDelay(0); - exportDoc->setOutputMimeType("image/png"); exportDoc->setFileBatchMode(true); KisImageSP exportImage = new KisImage(exportDoc->createUndoStore(), m_image->width(), m_image->height(), m_image->colorSpace(), QString()); exportImage->setResolution(m_image->xRes(), m_image->yRes()); exportDoc->setCurrentImage(exportImage); KisPaintLayer* paintLayer = new KisPaintLayer(exportImage, "paint device", OPACITY_OPAQUE_U8); exportImage->addNode(paintLayer, exportImage->rootLayer(), KisLayerSP(0)); } KisImageBuilder_Result CSVSaver::buildAnimation(QIODevice *io) { if (!m_image) { return KisImageBuilder_RESULT_EMPTY; } return encode(io); } void CSVSaver::cancel() { m_stop = true; } diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp index bd749712ce..18802e4d2d 100644 --- a/plugins/impex/libkra/kis_kra_saver.cpp +++ b/plugins/impex/libkra/kis_kra_saver.cpp @@ -1,452 +1,452 @@ /* This file is part of the KDE project * Copyright 2008 (C) Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_saver.h" #include "kis_kra_tags.h" #include "kis_kra_save_visitor.h" #include "kis_kra_savexml_visitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include #include "kis_dom_utils.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "KisProofingConfiguration.h" #include #include using namespace KRA; struct KisKraSaver::Private { public: KisDocument* doc; QMap nodeFileNames; QMap keyframeFilenames; QString imageName; QStringList errorMessages; }; KisKraSaver::KisKraSaver(KisDocument* document) : m_d(new Private) { m_d->doc = document; m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title"); if (m_d->imageName.isEmpty()) { m_d->imageName = i18n("Unnamed"); } } KisKraSaver::~KisKraSaver() { delete m_d; } QDomElement KisKraSaver::saveXML(QDomDocument& doc, KisImageSP image) { QDomElement imageElement = doc.createElement("IMAGE"); // Legacy! Q_ASSERT(image); imageElement.setAttribute(NAME, m_d->imageName); imageElement.setAttribute(MIME, NATIVE_MIMETYPE); imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width())); imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height())); imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id()); imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment")); // XXX: Save profile as blob inside the image, instead of the product name. if (image->profile() && image->profile()-> valid()) { imageElement.setAttribute(PROFILE, image->profile()->name()); } imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0)); imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0)); //now the proofing options: imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile)); imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel)); imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth)); imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent)); imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState)); quint32 count = 1; // We don't save the root layer, but it does count KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->doc->url().toLocalFile(), true); - visitor.setSelectedNodes(m_d->doc->activeNodes()); + visitor.setSelectedNodes({m_d->doc->preActivatedNode()}); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); m_d->nodeFileNames = visitor.nodeFileNames(); m_d->keyframeFilenames = visitor.keyframeFileNames(); saveBackgroundColor(doc, imageElement, image); saveWarningColor(doc, imageElement, image); saveCompositions(doc, imageElement, image); saveAssistantsList(doc, imageElement); saveGrid(doc,imageElement); saveGuides(doc,imageElement); saveAudio(doc,imageElement); QDomElement animationElement = doc.createElement("animation"); KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate()); KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->fullClipRange()); KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime()); imageElement.appendChild(animationElement); return imageElement; } bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external) { QMap::iterator it; for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) { const KisNode *node = it.key(); QString filename = it.value(); QString location = (external ? QString() : uri) + m_d->imageName + LAYER_PATH + filename; if (!saveNodeKeyframes(store, location, node)) { return false; } } return true; } bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node) { QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0"); QDomElement root = doc.documentElement(); KisKeyframeChannel *channel; Q_FOREACH (channel, node->keyframeChannels()) { QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]); root.appendChild(element); } if (store->open(location)) { QByteArray xml = doc.toByteArray(); store->write(xml); store->close(); } else { m_d->errorMessages << i18n("could not save keyframes"); return false; } return true; } bool KisKraSaver::saveBinaryData(KoStore* store, KisImageSP image, const QString &uri, bool external, bool autosave) { QString location; // Save the layers data KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames); if (external) visitor.setExternalUri(uri); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); if (!m_d->errorMessages.isEmpty()) { return false; } // saving annotations // XXX this only saves EXIF and ICC info. This would probably need // a redesign of the dtd of the krita file to do this more generally correct // e.g. have tags or so. KisAnnotationSP annotation = image->annotation("exif"); if (annotation) { location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } if (image->profile()) { const KoColorProfile *profile = image->profile(); KisAnnotationSP annotation; if (profile) { QByteArray profileRawData = profile->rawData(); if (!profileRawData.isEmpty()) { if (profile->type() == "icc") { annotation = new KisAnnotation(ICC, profile->name(), profile->rawData()); } else { annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData()); } } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } //This'll embed the profile used for proofing into the kra file. if (image->proofingConfiguration()) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile); if (proofingProfile && proofingProfile->valid()) { QByteArray proofingProfileRaw = proofingProfile->rawData(); if (!proofingProfileRaw.isEmpty()) { annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData()); } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } { KisPSDLayerStyleCollectionResource collection("not-nexists.asl"); KIS_ASSERT_RECOVER_NOOP(!collection.valid()); collection.collectAllLayerStyles(image->root()); if (collection.valid()) { location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->open(location)) { QBuffer aslBuffer; aslBuffer.open(QIODevice::WriteOnly); collection.saveToDevice(&aslBuffer); aslBuffer.close(); store->write(aslBuffer.buffer()); store->close(); } } } if (!autosave) { KisPaintDeviceSP dev = image->projection(); KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); } saveAssistants(store, uri,external); return true; } QStringList KisKraSaver::errorMessages() const { return m_d->errorMessages; } void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR); KoColor color = image->defaultProjectionColor(); QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64())); element.appendChild(e); } void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (image->proofingConfiguration()) { QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR); KoColor color = image->proofingConfiguration()->warningColor; color.toXML(doc, e); //QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); //e.setAttribute("ColorData", QString(colorData.toBase64())); element.appendChild(e); } } void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (!image->compositions().isEmpty()) { QDomElement e = doc.createElement("compositions"); Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) { composition->save(doc, e); } element.appendChild(e); } } bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external) { QString location; QMap assistantcounters; QByteArray data; QList assistants = m_d->doc->assistants(); QMap handlemap; if (!assistants.isEmpty()) { Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (!assistantcounters.contains(assist->id())){ assistantcounters.insert(assist->id(),0); } location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]); data = assist->saveXml(handlemap); store->open(location); store->write(data); store->close(); assistantcounters[assist->id()]++; } } return true; } bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element) { int count_ellipse = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0; QList assistants = m_d->doc->assistants(); if (!assistants.isEmpty()) { QDomElement assistantsElement = doc.createElement("assistants"); Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (assist->id() == "ellipse"){ assist->saveXmlList(doc, assistantsElement, count_ellipse); count_ellipse++; } else if (assist->id() == "spline"){ assist->saveXmlList(doc, assistantsElement, count_spline); count_spline++; } else if (assist->id() == "perspective"){ assist->saveXmlList(doc, assistantsElement, count_perspective); count_perspective++; } else if (assist->id() == "vanishing point"){ assist->saveXmlList(doc, assistantsElement, count_vanishingpoint); count_vanishingpoint++; } else if (assist->id() == "infinite ruler"){ assist->saveXmlList(doc, assistantsElement, count_infiniteruler); count_infiniteruler++; } else if (assist->id() == "parallel ruler"){ assist->saveXmlList(doc, assistantsElement, count_parallelruler); count_parallelruler++; } else if (assist->id() == "concentric ellipse"){ assist->saveXmlList(doc, assistantsElement, count_concentricellipse); count_concentricellipse++; } else if (assist->id() == "fisheye-point"){ assist->saveXmlList(doc, assistantsElement, count_fisheyepoint); count_fisheyepoint++; } else if (assist->id() == "ruler"){ assist->saveXmlList(doc, assistantsElement, count_ruler); count_ruler++; } } element.appendChild(assistantsElement); } return true; } bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element) { KisGridConfig config = m_d->doc->gridConfig(); if (!config.isDefault()) { QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid"); element.appendChild(gridElement); } return true; } bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element) { KisGuidesConfig guides = m_d->doc->guidesConfig(); if (guides.hasGuides()) { QDomElement guidesElement = guides.saveToXml(doc, "guides"); element.appendChild(guidesElement); } return true; } bool KisKraSaver::saveAudio(QDomDocument& doc, QDomElement& element) { const KisImageAnimationInterface *interface = m_d->doc->image()->animationInterface(); QString fileName = interface->audioChannelFileName(); if (fileName.isEmpty()) return true; if (!QFileInfo::exists(fileName)) { m_d->errorMessages << i18n("Audio channel file %1 doesn't exist!", fileName); return false; } const QDir documentDir = QFileInfo(m_d->doc->localFilePath()).absoluteDir(); KIS_ASSERT_RECOVER_RETURN_VALUE(documentDir.exists(), false); fileName = documentDir.relativeFilePath(fileName); fileName = QDir::fromNativeSeparators(fileName); KIS_ASSERT_RECOVER_RETURN_VALUE(!fileName.isEmpty(), false); QDomElement audioElement = doc.createElement("audio"); KisDomUtils::saveValue(&audioElement, "masterChannelPath", fileName); KisDomUtils::saveValue(&audioElement, "audioMuted", interface->isAudioMuted()); KisDomUtils::saveValue(&audioElement, "audioVolume", interface->audioVolume()); element.appendChild(audioElement); return true; } diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp index 99226ff310..3fdc7c998e 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp @@ -1,549 +1,549 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_saver_test.h" #include #include #include #include #include #include #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "util.h" #include "testutil.h" #include "kis_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "kis_layer_properties_icons.h" #include "kis_transform_mask_params_interface.h" #include #include #include void KisKraSaverTest::initTestCase() { KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns"); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraSaverTest::testCrashyShapeLayer() { /** * KisShapeLayer used to call setImage from its destructor and * therefore causing an infinite recursion (when at least one transparency * mask was preset. This testcase just checks that. */ //QScopedPointer doc(createCompleteDocument(true)); //Q_UNUSED(doc); } void KisKraSaverTest::testRoundTrip() { KisDocument* doc = createCompleteDocument(); KoColor bgColor(Qt::red, doc->image()->colorSpace()); doc->image()->setDefaultProjectionColor(bgColor); - doc->exportDocument(QUrl::fromLocalFile("roundtriptest.kra")); + doc->exportDocumentSync(QUrl::fromLocalFile("roundtriptest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); bool result = doc2->loadNativeFormat("roundtriptest.kra"); QVERIFY(result); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); // check whether the BG color is saved correctly QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor); // test round trip of a transform mask KisNode* tnode = TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data(); QVERIFY(tnode); KisTransformMask *tmask = dynamic_cast(tnode); QVERIFY(tmask); KisDumbTransformMaskParams *params = dynamic_cast(tmask->transformParams().data()); QVERIFY(params); QTransform t = params->testingGetTransform(); QCOMPARE(t, createTestingTransform()); delete doc2; delete doc; } void KisKraSaverTest::testSaveEmpty() { KisDocument* doc = createEmptyDocument(); - doc->exportDocument(QUrl::fromLocalFile("emptytest.kra")); + doc->exportDocumentSync(QUrl::fromLocalFile("emptytest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->loadNativeFormat("emptytest.kra"); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); delete doc2; delete doc; } #include #include "generator/kis_generator_registry.h" #include void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config) { TestUtil::ExternalImageChecker chk(testName, "fill_layer"); chk.setFuzzy(2); QScopedPointer doc(KisPart::instance()->createDocument()); // mask parent should be destructed before the document! QRect refRect(0,0,512,512); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KisSelectionSP selection; KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection); p.image->addNode(glayer, p.image->root(), KisNodeSP()); glayer->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); - doc->exportDocument(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra")); + doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_fill_layer_test.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "01_fill_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripFillLayerColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color"); Q_ASSERT(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); Q_ASSERT(config); QVariant v; v.setValue(KoColor(Qt::red, cs)); config->setProperty("color", v); testRoundTripFillLayerImpl("fill_layer_color", config); } void KisKraSaverTest::testRoundTripFillLayerPattern() { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); QVERIFY(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); QVERIFY(config); QVariant v; v.setValue(QString("11_drawed_furry.png")); config->setProperty("pattern", v); testRoundTripFillLayerImpl("fill_layer_pattern", config); } #include "kis_psd_layer_style.h" void KisKraSaverTest::testRoundTripLayerStyles() { TestUtil::ExternalImageChecker chk("kra_saver_test", "layer_styles"); QRect imageRect(0,0,512,512); // the document should be created before the image! QScopedPointer doc(KisPart::instance()->createDocument()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); image->addNode(layer1); image->addNode(layer2); image->addNode(layer3); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs)); layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs)); layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs)); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setAngle(-90); style->dropShadow()->setUseGlobalLight(false); layer1->setLayerStyle(style->clone()); style->dropShadow()->setAngle(180); style->dropShadow()->setUseGlobalLight(true); layer2->setLayerStyle(style->clone()); style->dropShadow()->setAngle(90); style->dropShadow()->setUseGlobalLight(false); layer3->setLayerStyle(style->clone()); image->initialRefreshGraph(); chk.checkImage(image, "00_initial_layers"); - doc->exportDocument(QUrl::fromLocalFile("roundtrip_layer_styles.kra")); + doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_layer_styles.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_layer_styles.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "00_initial_layers"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripAnimation() { QScopedPointer doc(KisPart::instance()->createDocument()); QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(layer1); layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs)); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs)); KUndo2Command parentCommand; layer1->enableAnimation(); KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); QVERIFY(rasterChannel); rasterChannel->addKeyframe(10, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(10); image->waitForDone(); layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(25, 15); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs)); rasterChannel->addKeyframe(20, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(100, 50); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs)); QVERIFY(!layer1->useInTimeline()); layer1->setUseInTimeline(true); doc->setCurrentImage(image); - doc->exportDocument(QUrl::fromLocalFile("roundtrip_animation.kra")); + doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_animation.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_animation.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild(); QVERIFY(node->inherits("KisPaintLayer")); KisPaintLayerSP layer2 = qobject_cast(node.data()); cs = layer2->paintDevice()->colorSpace(); QCOMPARE(image2->animationInterface()->currentTime(), 20); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel); QCOMPARE(channel->keyframeCount(), 3); image2->animationInterface()->switchCurrentTimeAsync(0); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128)); QCOMPARE(layer2->paintDevice()->x(), 0); QCOMPARE(layer2->paintDevice()->y(), 0); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs)); image2->animationInterface()->switchCurrentTimeAsync(10); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 25); QCOMPARE(layer2->paintDevice()->y(), 15); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs)); image2->animationInterface()->switchCurrentTimeAsync(20); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 100); QCOMPARE(layer2->paintDevice()->y(), 50); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs)); QVERIFY(layer2->useInTimeline()); } #include "lazybrush/kis_lazy_fill_tools.h" void KisKraSaverTest::testRoundTripColorizeMask() { QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16(); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); doc->setCurrentImage(image); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS); image->addNode(layer1); KisColorizeMaskSP mask = new KisColorizeMask(); image->addNode(mask, layer1); mask->initializeCompositeOp(); delete mask->setColorSpace(layer1->colorSpace()); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image); - doc->exportDocument(QUrl::fromLocalFile("roundtrip_colorize.kra")); + doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_colorize.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_colorize.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild()->firstChild(); KisColorizeMaskSP mask2 = dynamic_cast(node.data()); QVERIFY(mask2); QCOMPARE(mask2->compositeOpId(), mask->compositeOpId()); QCOMPARE(mask2->colorSpace(), mask->colorSpace()); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false); QList strokes = mask->fetchKeyStrokesDirect(); qDebug() << ppVar(strokes.size()); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[0].isTransparent, false); QCOMPARE(strokes[0].color.colorSpace(), weirdCS); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[1].isTransparent, false); QCOMPARE(strokes[1].color.colorSpace(), weirdCS); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QCOMPARE(strokes[2].isTransparent, true); QCOMPARE(strokes[2].color.colorSpace(), weirdCS); } #include "kis_shape_layer.h" #include #include void KisKraSaverTest::testRoundTripShapeLayer() { TestUtil::ExternalImageChecker chk("kra_saver_test", "shape_layer"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); shapeLayer->addShape(path); p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); - doc->exportDocument(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra")); + doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapelayer_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "01_shape_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripShapeSelection() { TestUtil::ExternalImageChecker chk("kra_saver_test", "shape_selection"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace())); KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds()); KisShapeSelection *shapeSelection = new KisShapeSelection(p.image, selection); selection->setShapeSelection(shapeSelection); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); shapeSelection->addShape(path); KisTransparencyMaskSP tmask = new KisTransparencyMask(); tmask->setSelection(selection); p.image->addNode(tmask, p.layer); tmask->setDirty(p.image->bounds()); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_shape_selection"); - doc->exportDocument(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra")); + doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapeselection_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "00_initial_shape_selection"); KisNodeSP node = doc2->image()->root()->firstChild()->firstChild(); KisTransparencyMask *newMask = dynamic_cast(node.data()); QVERIFY(newMask); QVERIFY(newMask->selection()->hasShapeSelection()); QVERIFY(chk.testPassed()); } QTEST_MAIN(KisKraSaverTest) diff --git a/plugins/impex/ora/ora_export.cc b/plugins/impex/ora/ora_export.cc index d8643402ff..85b4c2c5b0 100644 --- a/plugins/impex/ora/ora_export.cc +++ b/plugins/impex/ora/ora_export.cc @@ -1,112 +1,112 @@ /* * 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; 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 "ora_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ora_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_ora_export.json", registerPlugin();) OraExport::OraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } OraExport::~OraExport() { } bool hasShapeLayerChild(KisNodeSP node) { if (!node) return false; Q_FOREACH (KisNodeSP child, node->childNodes(QStringList(), KoProperties())) { if (child->inherits("KisShapeLayer") || child->inherits("KisGeneratorLayer") || child->inherits("KisCloneLayer")) { return true; } else { if (hasShapeLayerChild(child)) { return true; } } } return false; } KisImportExportFilter::ConversionStatus OraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); KisPaintDeviceSP pd = image->projection(); OraConverter oraConverter(document); KisImageBuilder_Result res; - if ((res = oraConverter.buildFile(io, image, document->activeNodes())) == KisImageBuilder_RESULT_OK) { + if ((res = oraConverter.buildFile(io, image, {document->preActivatedNode()})) == KisImageBuilder_RESULT_OK) { dbgFile << "success !"; return KisImportExportFilter::OK; } dbgFile << " Result =" << res; return KisImportExportFilter::InternalError; } void OraExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisAdjustmentLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "OpenRaster"); } #include diff --git a/plugins/impex/pdf/kis_pdf_import.cpp b/plugins/impex/pdf/kis_pdf_import.cpp index 99539311fe..f85d60264d 100644 --- a/plugins/impex/pdf/kis_pdf_import.cpp +++ b/plugins/impex/pdf/kis_pdf_import.cpp @@ -1,143 +1,141 @@ /* * Copyright (c) 2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_pdf_import.h" // poppler's headers #include // Qt's headers #include #include #include #include #include // KDE's headers #include #include #include #include #include #include // calligra's headers #include #include #include #include // krita's headers #include #include #include #include #include // plugins's headers #include "kis_pdf_import_widget.h" K_PLUGIN_FACTORY_WITH_JSON(PDFImportFactory, "krita_pdf_import.json", registerPlugin();) KisPDFImport::KisPDFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPDFImport::~KisPDFImport() { } KisPDFImport::ConversionStatus KisPDFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { Poppler::Document* pdoc = Poppler::Document::loadFromData(io->readAll()); if (!pdoc) { return KisPDFImport::InvalidFormat; } pdoc->setRenderHint(Poppler::Document::Antialiasing, true); pdoc->setRenderHint(Poppler::Document::TextAntialiasing, true); if (!pdoc) { dbgFile << "Error when reading the PDF"; return KisImportExportFilter::StorageCreationError; } while (pdoc->isLocked()) { KPasswordDialog dlg(0); dlg.setPrompt(i18n("A password is required to read that pdf")); dlg.setWindowTitle(i18n("A password is required to read that pdf")); if (dlg.exec() != QDialog::Accepted) { dbgFile << "Password canceled"; return KisImportExportFilter::StorageCreationError; } else pdoc->unlock(dlg.password().toLocal8Bit(), dlg.password().toLocal8Bit()); } KoDialog* kdb = new KoDialog(0); kdb->setCaption(i18n("PDF Import Options")); kdb->setModal(false); KisPDFImportWidget* wdg = new KisPDFImportWidget(pdoc, kdb); kdb->setMainWidget(wdg); QApplication::restoreOverrideCursor(); if (kdb->exec() == QDialog::Rejected) { delete pdoc; delete kdb; return KisImportExportFilter::StorageCreationError; // FIXME Cancel doesn't exist :( } // Create the krita image const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); int width = wdg->intWidth->value(); int height = wdg->intHeight->value(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, cs, "built image"); image->setResolution(wdg->intResolution->value() / 72.0, wdg->intResolution->value() / 72.0); // create a layer QList pages = wdg->pages(); - QPointer loadUpdater = document->progressUpdater()->startSubtask(1, "load"); - loadUpdater->setRange(0, pages.count()); for (QList::const_iterator it = pages.constBegin(); it != pages.constEnd(); ++it) { KisPaintLayer* layer = new KisPaintLayer(image.data(), i18n("Page %1", *it + 1), quint8_MAX); Poppler::Page* page = pdoc->page(*it); QImage img = page->renderToImage(wdg->intResolution->value(), wdg->intResolution->value(), 0, 0, width, height); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); delete page; image->addNode(layer, image->rootLayer(), 0); - loadUpdater->setProgress(*it + 1); + setProgress(qreal(*it + 1) * 100 / pages.count()); } document->setCurrentImage(image); delete pdoc; delete kdb; return KisImportExportFilter::OK; } #include "kis_pdf_import.moc" diff --git a/plugins/impex/psd/tests/kis_psd_test.cpp b/plugins/impex/psd/tests/kis_psd_test.cpp index d82e336259..1959e6608a 100644 --- a/plugins/impex/psd/tests/kis_psd_test.cpp +++ b/plugins/impex/psd/tests/kis_psd_test.cpp @@ -1,351 +1,351 @@ /* * 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 "kis_psd_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif #include #include "kis_group_layer.h" #include "kis_psd_layer_style.h" #include "kis_paint_device_debug_utils.h" void KisPSDTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } void KisPSDTest::testOpening() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); QScopedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); manager.setBatchMode(true); KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); QCOMPARE(status, KisImportExportFilter::OK); Q_ASSERT(doc->image()); } QSharedPointer openPsdDocument(const QFileInfo &fileInfo) { QSharedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); manager.setBatchMode(true); KisImportExportFilter::ConversionStatus status = manager.importDocument(fileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); return doc; } void KisPSDTest::testTransparencyMask() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/masks.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1)); - doc->setBackupFile(false); + doc->setFileBatchMode(true); - doc->setOutputMimeType("image/vnd.adobe.photoshop"); + doc->setMimeType("image/vnd.adobe.photoshop"); + QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_tmask.psd"); - bool retval = doc->saveAs(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath())); + bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop"); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1)); QVERIFY(doc->image()->root()->lastChild()); QVERIFY(doc->image()->root()->lastChild()->firstChild()); QVERIFY(doc->image()->root()->lastChild()->firstChild()->inherits("KisTransparencyMask")); } } void KisPSDTest::testOpenGrayscaleMultilayered() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/gray.psd"); //QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/100x100gray8.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); } void KisPSDTest::testOpenGroupLayers() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "group_layers.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisNodeSP node = TestUtil::findNode(doc->image()->root(), "Group 1 PT"); KisGroupLayer *group = dynamic_cast(node.data()); QVERIFY(group); QVERIFY(group->passThroughMode()); } void KisPSDTest::testOpenLayerStyles() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->dropShadow()); QVERIFY(layer->layerStyle()->dropShadow()->effectEnabled()); } void KisPSDTest::testOpenLayerStylesWithPattern() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); } void KisPSDTest::testOpenLayerStylesWithPatternMulti() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); } void KisPSDTest::testSaveLayerStylesWithPatternMulti() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); - - doc->setBackupFile(false); - doc->setOutputMimeType("image/vnd.adobe.photoshop"); + doc->setFileBatchMode(true); + const QByteArray mimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_save_styles.psd"); - bool retval = doc->saveAs(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath())); + bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), mimeType); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); //QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single")); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); } } void KisPSDTest::testOpeningFromOpenCanvas() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_krita_psd_from_opencanvas.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); QVERIFY(doc->image()->root()->firstChild()); } void KisPSDTest::testOpeningAllFormats() { QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files"); QDir dirSources(path); bool shouldFailTheTest = false; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { Q_ASSERT(sourceFileInfo.exists()); if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) { continue; } if (sourceFileInfo.fileName() != "ml_cmyk_16b.psd") { //continue; } //dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName()); QSharedPointer doc = openPsdDocument(sourceFileInfo); if (!doc->image()) { /** * 32bit images are expected to fail atm, their loading is not implemented */ if (!sourceFileInfo.fileName().contains("_32b")) { shouldFailTheTest = true; } errKrita << "FAILED to open" << sourceFileInfo.fileName(); continue; } // just check visually if the file loads fine KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), sourceFileInfo.fileName(), "dd"); } QVERIFY(!shouldFailTheTest); } void KisPSDTest::testSavingAllFormats() { QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files"); QDir dirSources(path); Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { Q_ASSERT(sourceFileInfo.exists()); if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) { continue; } if (sourceFileInfo.fileName() != "sl_rgb_8b.psd") { //continue; } dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName()); QSharedPointer doc = openPsdDocument(sourceFileInfo); if (!doc->image()) { errKrita << "FAILED to open" << sourceFileInfo.fileName(); continue; } QString baseName = sourceFileInfo.fileName(); - QString originalName = QString("%1_0orig").arg(baseName); - QString resultName = QString("%1_1result").arg(baseName); + //QString originalName = QString("%1_0orig").arg(baseName); + //QString resultName = QString("%1_1result").arg(baseName); QString tempPsdName = QString("%1_3interm.psd").arg(baseName); QImage refImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100)); // uncomment to do a visual check // KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), originalName, "dd"); - doc->setBackupFile(false); doc->setFileBatchMode(true); - doc->setOutputMimeType("image/vnd.adobe.photoshop"); + doc->setMimeType("image/vnd.adobe.photoshop"); + QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + tempPsdName); dbgKrita << "Saving" << ppVar(dstFileInfo.fileName()); - bool retval = doc->saveAs(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath())); + bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop"); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); // uncomment to do a visual check //KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), resultName, "dd"); QImage resultImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100)); QCOMPARE(resultImage, refImage); } } } QTEST_MAIN(KisPSDTest) diff --git a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp b/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp index 83acd478ae..8785178082 100644 --- a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp +++ b/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp @@ -1,289 +1,295 @@ /* * Copyright (c) 2004,2007,2009 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include "kis_auto_brush_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_aspect_ratio_locker.h" #define showSlider(input, step) input->setRange(input->minimum(), input->maximum(), step) #include KisAutoBrushWidget::KisAutoBrushWidget(QWidget *parent, const char* name) : KisWdgAutoBrush(parent, name) , m_autoBrush(0) , m_updateCompressor(new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE)) , m_fadeAspectLocker(new KisAspectRatioLocker()) { connect(m_updateCompressor.data(), SIGNAL(timeout()), SLOT(paramChanged())); connect((QObject*)comboBoxShape, SIGNAL(activated(int)), m_updateCompressor.data(), SLOT(start())); inputRadius->setRange(0, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2); inputRadius->setExponentRatio(3.0); inputRadius->setSingleStep(1); inputRadius->setValue(5); inputRadius->setSuffix(i18n(" px")); inputRadius->setBlockUpdateSignalOnDrag(true); connect(inputRadius, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputRatio->setRange(0.0, 1.0, 2); inputRatio->setSingleStep(0.1); inputRatio->setValue(1.0); inputRatio->setBlockUpdateSignalOnDrag(true); connect(inputRatio, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputHFade->setRange(0.0, 1.0, 2); inputHFade->setSingleStep(0.1); inputHFade->setValue(0.5); inputVFade->setRange(0.0, 1.0, 2); inputVFade->setSingleStep(0.1); inputVFade->setValue(0.5); aspectButton->setKeepAspectRatio(true); m_fadeAspectLocker->connectSpinBoxes(inputHFade, inputVFade, aspectButton); m_fadeAspectLocker->setBlockUpdateSignalOnDrag(true); connect(m_fadeAspectLocker.data(), SIGNAL(sliderValueChanged()), m_updateCompressor.data(), SLOT(start())); connect(m_fadeAspectLocker.data(), SIGNAL(aspectButtonChanged()), m_updateCompressor.data(), SLOT(start())); inputSpikes->setRange(2, 20); inputSpikes->setValue(2); inputSpikes->setBlockUpdateSignalOnDrag(true); connect(inputSpikes, SIGNAL(valueChanged(int)), m_updateCompressor.data(), SLOT(start())); inputRandomness->setRange(0, 100); inputRandomness->setValue(0); inputRandomness->setBlockUpdateSignalOnDrag(true); connect(inputRandomness, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputAngle->setRange(0, 360); inputAngle->setSuffix(QChar(Qt::Key_degree)); inputAngle->setValue(0); inputAngle->setBlockUpdateSignalOnDrag(true); connect(inputAngle, SIGNAL(valueChanged(int)), m_updateCompressor.data(), SLOT(start())); connect(spacingWidget, SIGNAL(sigSpacingChanged()), m_updateCompressor.data(), SLOT(start())); density->setRange(0, 100, 0); density->setSingleStep(1); density->setValue(100); density->setSuffix("%"); density->setBlockUpdateSignalOnDrag(true); connect(density, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); KisCubicCurve topLeftBottomRightLinearCurve; topLeftBottomRightLinearCurve.setPoint(0, QPointF(0.0, 1.0)); topLeftBottomRightLinearCurve.setPoint(1, QPointF(1.0, 0.0)); softnessCurve->setCurve(topLeftBottomRightLinearCurve); connect(softnessCurve, SIGNAL(modified()), m_updateCompressor.data(), SLOT(start())); m_brush = QImage(1, 1, QImage::Format_RGB32); connect(brushPreview, SIGNAL(clicked()), m_updateCompressor.data(), SLOT(start())); QList ids = KisMaskGenerator::maskGeneratorIds(); for (int i = 0; i < ids.size(); i++) { comboBoxMaskType->insertItem(i, ids[i].name()); } connect(comboBoxMaskType, SIGNAL(activated(int)), m_updateCompressor.data(), SLOT(start())); connect(comboBoxMaskType, SIGNAL(currentIndexChanged(int)), SLOT(setStackedWidget(int))); setStackedWidget(comboBoxMaskType->currentIndex()); brushPreview->setIconSize(QSize(100, 100)); connect(btnAntialiasing, SIGNAL(toggled(bool)), m_updateCompressor.data(), SLOT(start())); m_updateCompressor->start(); } KisAutoBrushWidget::~KisAutoBrushWidget() { } void KisAutoBrushWidget::resizeEvent(QResizeEvent *) { brushPreview->setMinimumHeight(brushPreview->width()); // dirty hack ! brushPreview->setMaximumHeight(brushPreview->width()); // dirty hack ! } void KisAutoBrushWidget::activate() { m_updateCompressor->start(); } void KisAutoBrushWidget::paramChanged() { KisMaskGenerator* kas; bool antialiasEdges = btnAntialiasing->isChecked(); if (comboBoxMaskType->currentIndex() == 2) { // gaussian brush if (comboBoxShape->currentIndex() == 0) { kas = new KisGaussCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } else { kas = new KisGaussRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } } else if (comboBoxMaskType->currentIndex() == 1) { // soft brush if (comboBoxShape->currentIndex() == 0) { kas = new KisCurveCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), softnessCurve->curve(), antialiasEdges); } else { kas = new KisCurveRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), softnessCurve->curve(), antialiasEdges); } } else {// default == 0 or any other if (comboBoxShape->currentIndex() == 0) { // use index compare instead of comparing a translatable string kas = new KisCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } else { kas = new KisRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } } Q_CHECK_PTR(kas); m_autoBrush = new KisAutoBrush(kas, inputAngle->value() / 180.0 * M_PI, inputRandomness->value() / 100.0, density->value() / 100.0); m_autoBrush->setSpacing(spacingWidget->spacing()); m_autoBrush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); m_brush = m_autoBrush->image(); + drawBrushPreviewArea(); + + emit sigBrushChanged(); +} + +void KisAutoBrushWidget::drawBrushPreviewArea() { QImage pi(m_brush); double coeff = 1.0; int bPw = brushPreview->width() - 3; if (pi.width() > bPw) { coeff = bPw / (double)pi.width(); } int bPh = brushPreview->height() - 3; if (pi.height() > coeff * bPh) { coeff = bPh / (double)pi.height(); } if (coeff < 1.0) { pi = pi.scaled((int)(coeff * pi.width()) , (int)(coeff * pi.height()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } QPixmap p = QPixmap::fromImage(pi); brushPreview->setIcon(QIcon(p)); - - emit sigBrushChanged(); } void KisAutoBrushWidget::setStackedWidget(int index) { if (index == 1) { stackedWidget->setCurrentIndex(1); } else { stackedWidget->setCurrentIndex(0); } } KisBrushSP KisAutoBrushWidget::brush() { return m_autoBrush; } void KisAutoBrushWidget::setBrush(KisBrushSP brush) { m_autoBrush = brush; m_brush = brush->image(); // XXX: lock, set and unlock the widgets. KisAutoBrush* aBrush = dynamic_cast(brush.data()); KisSignalsBlocker b1(comboBoxShape, comboBoxMaskType); KisSignalsBlocker b2(inputRadius, inputRatio, inputHFade, inputVFade, inputAngle, inputSpikes); KisSignalsBlocker b3(spacingWidget, inputRandomness, density, softnessCurve, btnAntialiasing); if (aBrush->maskGenerator()->type() == KisMaskGenerator::CIRCLE) { comboBoxShape->setCurrentIndex(0); } else if (aBrush->maskGenerator()->type() == KisMaskGenerator::RECTANGLE) { comboBoxShape->setCurrentIndex(1); } else { comboBoxShape->setCurrentIndex(2); } const int mastTypeIndex = comboBoxMaskType->findText(aBrush->maskGenerator()->name()); comboBoxMaskType->setCurrentIndex(mastTypeIndex); setStackedWidget(mastTypeIndex); // adjusting manually because the signals are blocked inputRadius->setValue(aBrush->maskGenerator()->diameter()); inputRatio->setValue(aBrush->maskGenerator()->ratio()); inputHFade->setValue(aBrush->maskGenerator()->horizontalFade()); inputVFade->setValue(aBrush->maskGenerator()->verticalFade()); inputAngle->setValue(aBrush->angle() * 180 / M_PI); inputSpikes->setValue(aBrush->maskGenerator()->spikes()); spacingWidget->setSpacing(aBrush->autoSpacingActive(), aBrush->autoSpacingActive() ? aBrush->autoSpacingCoeff() : aBrush->spacing()); inputRandomness->setValue(aBrush->randomness() * 100); density->setValue(aBrush->density() * 100); if (!aBrush->maskGenerator()->curveString().isEmpty()) { KisCubicCurve curve; curve.fromString(aBrush->maskGenerator()->curveString()); softnessCurve->setCurve(curve); } btnAntialiasing->setChecked(aBrush->maskGenerator()->antialiasEdges()); + + drawBrushPreviewArea(); // sync up what the brush preview area looks like } void KisAutoBrushWidget::setBrushSize(qreal dxPixels, qreal dyPixels) { Q_UNUSED(dyPixels); qreal newWidth = inputRadius->value() + dxPixels; newWidth = qMax(newWidth, qreal(0.1)); inputRadius->setValue(newWidth); } QSizeF KisAutoBrushWidget::brushSize() const { return QSizeF(inputRadius->value(), inputRadius->value() * inputRatio->value()); } #include "moc_kis_auto_brush_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_auto_brush_widget.h b/plugins/paintops/libpaintop/kis_auto_brush_widget.h index 03f74a00e2..afc65356f7 100644 --- a/plugins/paintops/libpaintop/kis_auto_brush_widget.h +++ b/plugins/paintops/libpaintop/kis_auto_brush_widget.h @@ -1,81 +1,83 @@ /* * Copyright (c) 2004,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. */ #ifndef _KIS_AUTO_BRUSH_WIDGET_H_ #define _KIS_AUTO_BRUSH_WIDGET_H_ #include #include #include "kritapaintop_export.h" #include "ui_wdgautobrush.h" #include class KisSignalCompressor; class KisAspectRatioLocker; class PAINTOP_EXPORT KisWdgAutoBrush : public QWidget, public Ui::KisWdgAutoBrush { Q_OBJECT public: KisWdgAutoBrush(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); } }; class PAINTOP_EXPORT KisAutoBrushWidget : public KisWdgAutoBrush { Q_OBJECT public: KisAutoBrushWidget(QWidget *parent, const char* name); ~KisAutoBrushWidget() override; void activate(); KisBrushSP brush(); void setBrush(KisBrushSP brush); void setBrushSize(qreal dxPixels, qreal dyPixels); QSizeF brushSize() const; + void drawBrushPreviewArea(); + private Q_SLOTS: void paramChanged(); void setStackedWidget(int); Q_SIGNALS: void sigBrushChanged(); protected: void resizeEvent(QResizeEvent *) override; private: QImage m_brush; KisBrushSP m_autoBrush; bool m_linkFade; QScopedPointer m_updateCompressor; QScopedPointer m_fadeAspectLocker; }; #endif diff --git a/plugins/tools/basictools/kis_tool_gradient.cc b/plugins/tools/basictools/kis_tool_gradient.cc index 3406b2e89d..d18aad3f94 100644 --- a/plugins/tools/basictools/kis_tool_gradient.cc +++ b/plugins/tools/basictools/kis_tool_gradient.cc @@ -1,301 +1,299 @@ /* * kis_tool_gradient.cc - part of Krita * * Copyright (c) 2002 Patrick Julien * Copyright (c) 2003 Boudewijn Rempt * Copyright (c) 2004-2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_gradient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resources_snapshot.h" KisToolGradient::KisToolGradient(KoCanvasBase * canvas) : KisToolPaint(canvas, KisCursor::load("tool_gradient_cursor.png", 6, 6)) { setObjectName("tool_gradient"); m_startPos = QPointF(0, 0); m_endPos = QPointF(0, 0); m_reverse = false; m_shape = KisGradientPainter::GradientShapeLinear; m_repeat = KisGradientPainter::GradientRepeatNone; m_antiAliasThreshold = 0.2; } KisToolGradient::~KisToolGradient() { } void KisToolGradient::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolGradient::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolGradient::paint(QPainter &painter, const KoViewConverter &converter) { if (mode() == KisTool::PAINT_MODE && m_startPos != m_endPos) { qreal sx, sy; converter.zoom(&sx, &sy); painter.scale(sx / currentImage()->xRes(), sy / currentImage()->yRes()); paintLine(painter); } } void KisToolGradient::beginPrimaryAction(KoPointerEvent *event) { if (!nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); m_startPos = convertToPixelCoordAndSnap(event, QPointF(), false); m_endPos = m_startPos; } void KisToolGradient::continuePrimaryAction(KoPointerEvent *event) { /** * TODO: The gradient tool is still not in strokes, so the end of * its action can call processEvent(), which would result in * nested event hadler calls. Please uncomment this line * when the tool is ported to strokes. */ //CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false); QRectF bound(m_startPos, m_endPos); canvas()->updateCanvas(convertToPt(bound.normalized())); if (event->modifiers() == Qt::ShiftModifier) { m_endPos = straightLine(pos); } else { m_endPos = pos; } bound.setTopLeft(m_startPos); bound.setBottomRight(m_endPos); canvas()->updateCanvas(convertToPt(bound.normalized())); } void KisToolGradient::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!currentNode() || !blockUntilOperationsFinished()) return; if (m_startPos == m_endPos) { return; } KisPaintDeviceSP device; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), this->canvas()->resourceManager()); if (image && (device = resources->currentNode()->paintDevice())) { QApplication::setOverrideCursor(Qt::BusyCursor); KUndo2MagicString actionName = kundo2_i18n("Gradient"); KisUndoAdapter *undoAdapter = image->undoAdapter(); undoAdapter->beginMacro(actionName); KisGradientPainter painter(device, resources->activeSelection()); resources->setupPainter(&painter); painter.beginTransaction(); KisCanvas2 * canvas = dynamic_cast(this->canvas()); - KoProgressUpdater * updater = canvas->viewManager()->createProgressUpdater(KoProgressUpdater::Unthreaded); + KoUpdaterPtr updater = canvas->viewManager()->createUnthreadedUpdater(i18nc("@info:progress", "Gradient...")); - updater->start(100, i18nc("@info:progress", "Gradient...")); - painter.setProgress(updater->startSubtask()); + painter.setProgress(updater); painter.setGradientShape(m_shape); painter.paintGradient(m_startPos, m_endPos, m_repeat, m_antiAliasThreshold, m_reverse, 0, 0, image->width(), image->height()); painter.endTransaction(undoAdapter); undoAdapter->endMacro(); QApplication::restoreOverrideCursor(); currentNode()->setDirty(); notifyModified(); - delete updater; } canvas()->updateCanvas(convertToPt(currentImage()->bounds())); } QPointF KisToolGradient::straightLine(QPointF point) { QPointF comparison = point - m_startPos; QPointF result; if (fabs(comparison.x()) > fabs(comparison.y())) { result.setX(point.x()); result.setY(m_startPos.y()); } else { result.setX(m_startPos.x()); result.setY(point.y()); } return result; } void KisToolGradient::paintLine(QPainter& gc) { if (canvas()) { QPen old = gc.pen(); QPen pen(Qt::SolidLine); gc.setPen(pen); gc.drawLine(m_startPos, m_endPos); gc.setPen(old); } } QWidget* KisToolGradient::createOptionWidget() { QWidget *widget = KisToolPaint::createOptionWidget(); Q_CHECK_PTR(widget); widget->setObjectName(toolId() + " option widget"); // Make sure to create the connections last after everything is set up. The initialized values // won't be loaded from the configuration file if you add the widget before the connection m_lbShape = new QLabel(i18n("Shape:"), widget); m_cmbShape = new KComboBox(widget); m_cmbShape->setObjectName("shape_combo"); m_cmbShape->addItem(i18nc("the gradient will be drawn linearly", "Linear")); m_cmbShape->addItem(i18nc("the gradient will be drawn bilinearly", "Bi-Linear")); m_cmbShape->addItem(i18nc("the gradient will be drawn radially", "Radial")); m_cmbShape->addItem(i18nc("the gradient will be drawn in a square around a centre", "Square")); m_cmbShape->addItem(i18nc("the gradient will be drawn as an assymmetric cone", "Conical")); m_cmbShape->addItem(i18nc("the gradient will be drawn as a symmetric cone", "Conical Symmetric")); m_cmbShape->addItem(i18nc("the gradient will be drawn in a selection outline", "Shaped")); addOptionWidgetOption(m_cmbShape, m_lbShape); connect(m_cmbShape, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetShape(int))); m_lbRepeat = new QLabel(i18n("Repeat:"), widget); m_cmbRepeat = new KComboBox(widget); m_cmbRepeat->setObjectName("repeat_combo"); m_cmbRepeat->addItem(i18nc("The gradient will not repeat", "None")); m_cmbRepeat->addItem(i18nc("The gradient will repeat forwards", "Forwards")); m_cmbRepeat->addItem(i18nc("The gradient will repeat alternatingly", "Alternating")); addOptionWidgetOption(m_cmbRepeat, m_lbRepeat); connect(m_cmbRepeat, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetRepeat(int))); m_lbAntiAliasThreshold = new QLabel(i18n("Anti-alias threshold:"), widget); m_slAntiAliasThreshold = new KisDoubleSliderSpinBox(widget); m_slAntiAliasThreshold->setObjectName("threshold_slider"); m_slAntiAliasThreshold->setRange(0, 1, 3); addOptionWidgetOption(m_slAntiAliasThreshold, m_lbAntiAliasThreshold); connect(m_slAntiAliasThreshold, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAntiAliasThreshold(qreal))); m_ckReverse = new QCheckBox(i18nc("the gradient will be drawn with the color order reversed", "Reverse"), widget); m_ckReverse->setObjectName("reverse_check"); connect(m_ckReverse, SIGNAL(toggled(bool)), this, SLOT(slotSetReverse(bool))); addOptionWidgetOption(m_ckReverse); widget->setFixedHeight(widget->sizeHint().height()); // load configuration settings into widget (updating UI will update internal variables from signals/slots) m_ckReverse->setChecked((bool)m_configGroup.readEntry("reverse", false)); m_cmbShape->setCurrentIndex((int)m_configGroup.readEntry("shape", 0)); m_cmbRepeat->setCurrentIndex((int)m_configGroup.readEntry("repeat", 0)); m_slAntiAliasThreshold->setValue((qreal)m_configGroup.readEntry("antialiasThreshold", 0.0)); return widget; } void KisToolGradient::slotSetShape(int shape) { m_shape = static_cast(shape); m_configGroup.writeEntry("shape", shape); } void KisToolGradient::slotSetRepeat(int repeat) { m_repeat = static_cast(repeat); m_configGroup.writeEntry("repeat", repeat); } void KisToolGradient::slotSetReverse(bool state) { m_reverse = state; m_configGroup.writeEntry("reverse", state); } void KisToolGradient::slotSetAntiAliasThreshold(qreal value) { m_antiAliasThreshold = value; m_configGroup.writeEntry("antialiasThreshold", value); } diff --git a/plugins/tools/basictools/kis_tool_line.h b/plugins/tools/basictools/kis_tool_line.h index 281b4b0356..f376ea9eec 100644 --- a/plugins/tools/basictools/kis_tool_line.h +++ b/plugins/tools/basictools/kis_tool_line.h @@ -1,136 +1,135 @@ /* * 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; 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/tool_smart_patch/kis_tool_smart_patch.h b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.h index 69ebf3e205..b4e46fb7c5 100644 --- a/plugins/tools/tool_smart_patch/kis_tool_smart_patch.h +++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.h @@ -1,110 +1,109 @@ /* * Copyright (c) 2017 Eugene Ingerman * * 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_SMART_PATCH_H_ #define KIS_TOOL_SMART_PATCH_H_ #include #include #include "kis_tool_paint.h" #include "KoToolFactoryBase.h" #include #include #include #include #include #include class KActionCollection; class KoCanvasBase; class KisPaintInformation; class KisSpacingInfomation; class KisToolSmartPatch : public KisToolPaint { Q_OBJECT public: KisToolSmartPatch(KoCanvasBase * canvas); ~KisToolSmartPatch() override; QWidget * createOptionWidget() override; void activatePrimaryAction() override; void deactivatePrimaryAction() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void paint(QPainter &painter, const KoViewConverter &converter) override; int flags() const override { return KisTool::FLAG_USES_CUSTOM_SIZE; } protected Q_SLOTS: void resetCursorStyle() override; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; private: //QRect inpaintImage(KisPaintDeviceSP maskDev, KisPaintDeviceSP imageDev); QPainterPath getBrushOutlinePath(const QPointF &documentPos, const KoPointerEvent *event); QPainterPath brushOutline(); void requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event) override; private: struct Private; class InpaintCommand; const QScopedPointer m_d; void addMaskPath(KoPointerEvent *event); }; class KisToolSmartPatchFactory : public KoToolFactoryBase { public: KisToolSmartPatchFactory() : KoToolFactoryBase("KritaShape/KisToolSmartPatch") { setToolTip(i18n("Smart Patch Tool")); setSection(TOOL_TYPE_FILL); setIconName(koIconNameCStr("krita_tool_smart_patch")); - setShortcut(QKeySequence(Qt::Key_Shift + Qt::Key_I)); setPriority(4); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSmartPatchFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSmartPatch(canvas); } }; #endif // KIS_TOOL_SMART_PATCH_H_ diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index 0e80db774a..37f492b3df 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1182 +1,1182 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "kis_layer_utils.h" #include #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_changesTracker(&m_transaction) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(const QPointF&)), SLOT(cursorOutlineUpdateRequested(const QPointF&))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged()), this, SLOT(slotTrackerChangedConfig())); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeData.strokeId()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } gc.save(); if (m_optionsWidget && m_optionsWidget->showDecorations()) { gc.setOpacity(0.3); gc.fillPath(m_selectionPath, Qt::black); } gc.restore(); currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeData.strokeId()) { useCursor(KisCursor::pointingHandCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } if (!m_strokeData.strokeId()) { startStroke(m_currentArgs.mode(), false); } else { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activatePrimaryAction() { currentStrategy()->activatePrimaryAction(); setFunctionalCursor(); } void KisToolTransform::deactivatePrimaryAction() { currentStrategy()->deactivatePrimaryAction(); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); - if (mode() != KisTool::PAINT_MODE) { + if (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } bool KisToolTransform::tryInitTransformModeFromNode(KisNodeSP node) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { m_currentArgs = adapter->transformArgs(); initGuiAfterTransformMode(); result = true; } } return result; } bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode) { bool result = false; const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); KisNodeSP oldRootNode; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode) && args->mode() == mode && oldRootNode == currentNode) { args->saveContinuedState(); image()->undoAdapter()->undoLastCommand(); // FIXME: can we make it async? image()->waitForDone(); forceRepaintDelayedLayers(oldRootNode); result = true; } return result; } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { // NOTE: we are requesting an old value of m_currentArgs variable // here, which is global, don't forget about this on higher // levels. QString filterId = m_currentArgs.filterId(); m_currentArgs = ToolTransformArgs(); m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric()); m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric()); if (mode == ToolTransformArgs::FREE_TRANSFORM) { m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { m_currentArgs.setMode(ToolTransformArgs::WARP); m_optionsWidget->setDefaultWarpPoints(); m_currentArgs.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { m_currentArgs.setMode(ToolTransformArgs::CAGE); m_currentArgs.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { m_currentArgs.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = m_transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::updateSelectionPath() { m_selectionPath = QPainterPath(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); QPainterPath selectionOutline; KisSelectionSP selection = resources->activeSelection(); if (selection && selection->outlineCacheValid()) { selectionOutline = selection->outlineCache(); } else { selectionOutline.addRect(m_selectedPortionCache->exactBounds()); } const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); m_selectionPath = i2f.map(selectionOutline); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeData.strokeId()) return; m_changesTracker.requestUndo(); } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { cancelStroke(); } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeData.strokeId()); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) { return; } /** * FIXME: The transform tool is not completely asynchronous, it * needs the content of the layer for creation of the stroke * strategy. It means that we cannot start a new stroke until the * previous one is finished. Ideally, we should create the * m_selectedPortionCache and m_selectionPath somewhere in the * stroke and pass it to the tool somehow. But currently, we will * just disable starting a new stroke asynchronously */ if (image()->tryBarrierLock()) { image()->unlock(); } else { return; } /** * We must ensure that the currently selected subtree * has finished all its updates. */ forceRepaintDelayedLayers(currentNode); ToolTransformArgs fetchedArgs; bool fetchedFromCommand = false; if (!forceReset) { fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } QList nodesList = fetchNodesList(mode, currentNode, m_workRecursively); if (nodesList.isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selected layer cannot be transformed with active transformation mode "), koIcon("object-locked"), 4000, KisFloatingMessage::High); return; } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, resources->activeSelection(), image().data()); KisPaintDeviceSP previewDevice = strategy->previewDevice(); KisSelectionSP selection = strategy->realSelection(); QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds(); if (!selection && resources->activeSelection()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); } if (srcRect.isEmpty()) { delete strategy; KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); return; } m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList); initThumbnailImage(previewDevice); updateSelectionPath(); if (!forceReset && fetchedFromCommand) { m_currentArgs = fetchedArgs; initGuiAfterTransformMode(); } else if (forceReset || !tryInitTransformModeFromNode(currentNode)) { initTransformMode(mode); } m_strokeData = StrokeData(image()->startStroke(strategy)); bool haveInvisibleNodes = clearDevices(nodesList); if (haveInvisibleNodes) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } Q_ASSERT(m_changesTracker.isEmpty()); commitChanges(); } void KisToolTransform::endStroke() { if (!m_strokeData.strokeId()) return; if (!m_currentArgs.isIdentity()) { transformClearedDevices(); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::SELECTION, m_currentArgs, m_transaction.rootNode())); // root node is used for progress only image()->endStroke(m_strokeData.strokeId()); } else { image()->cancelStroke(m_strokeData.strokeId()); } m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::cancelStroke() { if (!m_strokeData.strokeId()) return; if (m_currentArgs.continuedTransform()) { m_currentArgs.restoreContinuedState(); endStroke(); } else { image()->cancelStroke(m_strokeData.strokeId()); m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } } void KisToolTransform::commitChanges() { if (!m_strokeData.strokeId()) return; m_changesTracker.commitConfig(m_currentArgs); } void KisToolTransform::slotTrackerChangedConfig() { slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable() && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool KisToolTransform::clearDevices(const QList &nodes) { bool haveInvisibleNodes = false; Q_FOREACH (KisNodeSP node, nodes) { haveInvisibleNodes |= !node->visible(false); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::ClearSelectionData(node)); /** * It might happen that the editablity state of the node would * change during the stroke, so we need to save the set of * applicable nodes right in the beginning of the processing */ m_strokeData.addClearedNode(node); } return haveInvisibleNodes; } void KisToolTransform::transformClearedDevices() { Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) { KIS_ASSERT_RECOVER_RETURN(node); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::PAINT_DEVICE, m_currentArgs, node)); } } QWidget* KisToolTransform::createOptionWidget() { m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeData.strokeId()) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedArgs.mode(), true); } void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root) { KIS_SAFE_ASSERT_RECOVER_RETURN(root); KisLayerUtils::recursiveApplyNodes(root, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); image()->waitForDone(); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } diff --git a/sdk/tests/testutil.h b/sdk/tests/testutil.h index 75c981fc7c..fc19b75cf3 100644 --- a/sdk/tests/testutil.h +++ b/sdk/tests/testutil.h @@ -1,419 +1,427 @@ /* * 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 TEST_UTIL #define TEST_UTIL #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_graph_listener.h" #include "kis_iterator_ng.h" #include "kis_image.h" #include "testing_nodes.h" #ifndef FILES_DATA_DIR #define FILES_DATA_DIR "." #endif #ifndef FILES_DEFAULT_DATA_DIR #define FILES_DEFAULT_DATA_DIR "." #endif #include "qimage_test_util.h" /** * Routines that are useful for writing efficient tests */ namespace TestUtil { inline KisNodeSP findNode(KisNodeSP root, const QString &name) { if(root->name() == name) return root; KisNodeSP child = root->firstChild(); while (child) { if((root = findNode(child, name))) return root; child = child->nextSibling(); } return KisNodeSP(); } inline void dumpNodeStack(KisNodeSP node, QString prefix = QString("\t")) { qDebug() << node->name(); KisNodeSP child = node->firstChild(); while (child) { if (child->childCount() > 0) { dumpNodeStack(child, prefix + "\t"); } else { qDebug() << prefix << child->name(); } child = child->nextSibling(); } } class TestProgressBar : public KoProgressProxy { public: TestProgressBar() : m_min(0), m_max(0), m_value(0) {} int maximum() const override { return m_max; } void setValue(int value) override { m_value = value; } void setRange(int min, int max) override { m_min = min; m_max = max; } void setFormat(const QString &format) override { m_format = format; } + void setAutoNestedName(const QString &name) { + m_autoNestedName = name; + KoProgressProxy::setAutoNestedName(name); + } + int min() { return m_min; } int max() { return m_max; } int value() { return m_value; } QString format() { return m_format; } + QString autoNestedName() { return m_autoNestedName; } + private: int m_min; int m_max; int m_value; QString m_format; + QString m_autoNestedName; }; inline bool comparePaintDevices(QPoint & pt, const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2) { // QTime t; // t.start(); QRect rc1 = dev1->exactBounds(); QRect rc2 = dev2->exactBounds(); if (rc1 != rc2) { pt.setX(-1); pt.setY(-1); } KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width()); KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width()); int pixelSize = dev1->pixelSize(); for (int y = 0; y < rc1.height(); ++y) { do { if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) return false; } while (iter1->nextPixel() && iter2->nextPixel()); iter1->nextRow(); iter2->nextRow(); } // qDebug() << "comparePaintDevices time elapsed:" << t.elapsed(); return true; } template inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2, channel_type alphaThreshold = 0) { QRect rc1 = dev1->exactBounds(); QRect rc2 = dev2->exactBounds(); if (rc1 != rc2) { qDebug() << "Devices have different size" << ppVar(rc1) << ppVar(rc2); return false; } KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width()); KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width()); int pixelSize = dev1->pixelSize(); for (int y = 0; y < rc1.height(); ++y) { do { if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) { const channel_type* p1 = reinterpret_cast(iter1->oldRawData()); const channel_type* p2 = reinterpret_cast(iter2->oldRawData()); if (p1[3] < alphaThreshold && p2[3] < alphaThreshold) continue; qDebug() << "Failed compare paint devices:" << iter1->x() << iter1->y(); qDebug() << "src:" << p1[0] << p1[1] << p1[2] << p1[3]; qDebug() << "dst:" << p2[0] << p2[1] << p2[2] << p2[3]; return false; } } while (iter1->nextPixel() && iter2->nextPixel()); iter1->nextRow(); iter2->nextRow(); } return true; } #ifdef FILES_OUTPUT_DIR struct ExternalImageChecker { ExternalImageChecker(const QString &prefix, const QString &testName) : m_prefix(prefix), m_testName(testName), m_success(true), m_maxFailingPixels(100), m_fuzzy(1) { } void setMaxFailingPixels(int value) { m_maxFailingPixels = value; } void setFuzzy(int fuzzy){ m_fuzzy = fuzzy; } bool testPassed() const { return m_success; } inline bool checkDevice(KisPaintDeviceSP device, KisImageSP image, const QString &caseName) { bool result = checkQImageExternal(device->convertToQImage(0, image->bounds()), m_testName, m_prefix, caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels); m_success &= result; return result; } inline bool checkImage(KisImageSP image, const QString &testName) { bool result = checkDevice(image->projection(), image, testName); m_success &= result; return result; } private: QString m_prefix; QString m_testName; bool m_success; int m_maxFailingPixels; int m_fuzzy; }; #endif inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y) { KisHLineConstIteratorSP iter = dev->createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->oldRawData(); return *pix; } inline void alphaDeviceSetPixel(KisPaintDeviceSP dev, qint32 x, qint32 y, quint8 s) { KisHLineIteratorSP iter = dev->createHLineIteratorNG(x, y, 1); quint8 *pix = iter->rawData(); *pix = s; } inline bool checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev, const QRect &rc, quint8 expected) { KisHLineIteratorSP it = dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); for (int y = rc.y(); y < rc.y() + rc.height(); y++) { for (int x = rc.x(); x < rc.x() + rc.width(); x++) { if(*((quint8*)it->rawData()) != expected) { errKrita << "At point:" << x << y; errKrita << "Expected pixel:" << expected; errKrita << "Actual pixel: " << *((quint8*)it->rawData()); return false; } it->nextPixel(); } it->nextRow(); } return true; } class TestNode : public DefaultNode { Q_OBJECT public: KisNodeSP clone() const override { return KisNodeSP(new TestNode(*this)); } }; class TestGraphListener : public KisNodeGraphListener { public: void aboutToAddANode(KisNode *parent, int index) override { KisNodeGraphListener::aboutToAddANode(parent, index); beforeInsertRow = true; } void nodeHasBeenAdded(KisNode *parent, int index) override { KisNodeGraphListener::nodeHasBeenAdded(parent, index); afterInsertRow = true; } void aboutToRemoveANode(KisNode *parent, int index) override { KisNodeGraphListener::aboutToRemoveANode(parent, index); beforeRemoveRow = true; } void nodeHasBeenRemoved(KisNode *parent, int index) override { KisNodeGraphListener::nodeHasBeenRemoved(parent, index); afterRemoveRow = true; } void aboutToMoveNode(KisNode *parent, int oldIndex, int newIndex) override { KisNodeGraphListener::aboutToMoveNode(parent, oldIndex, newIndex); beforeMove = true; } void nodeHasBeenMoved(KisNode *parent, int oldIndex, int newIndex) override { KisNodeGraphListener::nodeHasBeenMoved(parent, oldIndex, newIndex); afterMove = true; } bool beforeInsertRow; bool afterInsertRow; bool beforeRemoveRow; bool afterRemoveRow; bool beforeMove; bool afterMove; void resetBools() { beforeRemoveRow = false; afterRemoveRow = false; beforeInsertRow = false; afterInsertRow = false; beforeMove = false; afterMove = false; } }; } #include #include #include "kis_undo_stores.h" namespace TestUtil { struct MaskParent { MaskParent(const QRect &_imageRect = QRect(0,0,512,512)) : imageRect(_imageRect) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); undoStore = new KisSurrogateUndoStore(); image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "test image"); layer = KisPaintLayerSP(new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8)); image->addNode(KisNodeSP(layer.data())); } KisSurrogateUndoStore *undoStore; const QRect imageRect; KisImageSP image; KisPaintLayerSP layer; }; } namespace TestUtil { class MeasureAvgPortion { public: MeasureAvgPortion(int period) : m_period(period), m_val(0), m_total(0), m_cycles(0) { } ~MeasureAvgPortion() { printValues(true); } void addVal(int x) { m_val += x; } void addTotal(int x) { m_total += x; m_cycles++; printValues(); } private: void printValues(bool force = false) { if (m_cycles > m_period || force) { qDebug() << "Val / Total:" << qreal(m_val) / qreal(m_total); qDebug() << "Avg. Val: " << qreal(m_val) / m_cycles; qDebug() << "Avg. Total: " << qreal(m_total) / m_cycles; qDebug() << ppVar(m_val) << ppVar(m_total) << ppVar(m_cycles); m_val = 0; m_total = 0; m_cycles = 0; } } private: int m_period; qint64 m_val; qint64 m_total; qint64 m_cycles; }; QStringList getHierarchy(KisNodeSP root, const QString &prefix = ""); bool checkHierarchy(KisNodeSP root, const QStringList &expected); } #endif