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_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/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/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/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/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index a3c4977cf9..89440d7c17 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,168 +1,169 @@ org.kde.krita.desktop CC0-1.0 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 Цифровое рисование. Творческая свобода 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/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 d9d0ab2ca8..caa307529a 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,555 +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; 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(true, 0); } bool Document::saveAs(const QString &filename) { if (!d->document) return false; 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 1922e6e24b..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::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 102f849a03..747740f112 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,567 +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 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(); 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 2117e6e4a8..02270710ae 100644 --- a/libs/libkis/Palette.cpp +++ b/libs/libkis/Palette.cpp @@ -1,113 +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 fef32bf1f5..9246763b29 100644 --- a/libs/libkis/Palette.h +++ b/libs/libkis/Palette.h @@ -1,136 +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(); - //setcomment + /** + * @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/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/store/CMakeLists.txt b/libs/store/CMakeLists.txt index 08d6323c34..719fe00a79 100644 --- a/libs/store/CMakeLists.txt +++ b/libs/store/CMakeLists.txt @@ -1,23 +1,24 @@ add_subdirectory(tests) set(kritastore_LIB_SRCS KoDirectoryStore.cpp + KoStoreDevice.cpp KoLZF.cpp KoStore.cpp KoXmlNS.cpp KoXmlReader.cpp KoXmlWriter.cpp KoZipStore.cpp StoreDebug.cpp ) add_library(kritastore SHARED ${kritastore_LIB_SRCS}) generate_export_header(kritastore BASE_NAME kritastore) target_link_libraries(kritastore kritaversion kritaglobal Qt5::Xml Qt5::Gui KF5::Archive) set_target_properties(kritastore PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritastore ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/libs/store/KoStoreDevice.cpp b/libs/store/KoStoreDevice.cpp new file mode 100644 index 0000000000..99511c5d0a --- /dev/null +++ b/libs/store/KoStoreDevice.cpp @@ -0,0 +1,24 @@ +/* This file is part of the KDE project + Copyright (C) 2000 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 "KoStoreDevice.h" + +KoStoreDevice::~KoStoreDevice() +{ +} diff --git a/libs/store/KoStoreDevice.h b/libs/store/KoStoreDevice.h index e4379588da..b4e6985fb0 100644 --- a/libs/store/KoStoreDevice.h +++ b/libs/store/KoStoreDevice.h @@ -1,85 +1,88 @@ /* This file is part of the KDE project Copyright (C) 2000 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 koStoreDevice_h #define koStoreDevice_h #include "KoStore.h" +#include + /** * This class implements a QIODevice around KoStore, so that * it can be used to create a QDomDocument from it, to be written or read * using QDataStream or to be written using QTextStream */ -class KoStoreDevice : public QIODevice +class KRITASTORE_EXPORT KoStoreDevice : public QIODevice { + Q_OBJECT public: /// Note: KoStore::open() should be called before calling this. explicit KoStoreDevice(KoStore * store) : m_store(store) { // calligra-1.x behavior compat: a KoStoreDevice is automatically open setOpenMode(m_store->mode() == KoStore::Read ? QIODevice::ReadOnly : QIODevice::WriteOnly); } - ~KoStoreDevice() override {} + ~KoStoreDevice() override; bool isSequential() const override { return true; } bool open(OpenMode m) override { setOpenMode(m); if (m & QIODevice::ReadOnly) return (m_store->mode() == KoStore::Read); if (m & QIODevice::WriteOnly) return (m_store->mode() == KoStore::Write); return false; } void close() override {} qint64 size() const override { if (m_store->mode() == KoStore::Read) return m_store->size(); else return 0xffffffff; } // See QIODevice qint64 pos() const override { return m_store->pos(); } bool seek(qint64 pos) override { return m_store->seek(pos); } bool atEnd() const override { return m_store->atEnd(); } protected: KoStore *m_store; qint64 readData(char *data, qint64 maxlen) override { return m_store->read(data, maxlen); } qint64 writeData(const char *data, qint64 len) override { return m_store->write(data, len); } }; #endif diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 8d1501c17d..f24b3547e2 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,555 +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/KisDocument.cpp b/libs/ui/KisDocument.cpp index 0b3dde7d66..6f693dc69f 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1642 +1,1642 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)), // deleted by QObject importExportManager(new KisImportExportManager(q)), // deleted manually undoStack(new UndoStack(q)), // deleted by QObject m_bAutoDetectedMime(false), modified(false), readwrite(true), firstMod(QDateTime::currentDateTime()), lastMod(firstMod), nserver(new KisNameServer(1)), imageIdleWatcher(2000 /*ms*/), savingLock(&savingMutex) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)), unit(rhs.unit), importExportManager(new KisImportExportManager(q)), mimeType(rhs.mimeType), outputMimeType(rhs.outputMimeType), undoStack(new UndoStack(q)), guidesConfig(rhs.guidesConfig), m_bAutoDetectedMime(rhs.m_bAutoDetectedMime), m_url(rhs.m_url), m_file(rhs.m_file), modified(rhs.modified), readwrite(rhs.readwrite), firstMod(rhs.firstMod), lastMod(rhs.lastMod), nserver(new KisNameServer(*rhs.nserver)), preActivatedNode(0), // the node is from another hierarchy! imageIdleWatcher(2000 /*ms*/), assistants(rhs.assistants), // WARNING: assistants should not store pointers to the document! gridConfig(rhs.gridConfig), savingLock(&savingMutex) { } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; KisGridConfig gridConfig; StdLockableWrapper savingLock; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true)); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer.disconnect(this); d->autoSaveTimer.stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg; if (cfg.backupFile() && filePathInfo.exists()) { KBackup::backupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; return exportDocumentImpl(ExportFileJob(url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { 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); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->importExportManager->batchMode(); } void KisDocument::setFileBatchMode(const bool batchMode) { d->importExportManager->setBatchMode(batchMode); } KisDocument* KisDocument::lockAndCloneForSaving() { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument(lockAndCloneForSaving()); // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSave() { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); bool started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0); if (!started) { const int emergencyAutoSaveInterval = 10; // sec setAutoSaveDelay(emergencyAutoSaveInterval); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { const int emergencyAutoSaveInterval = 10; // sec setAutoSaveDelay(emergencyAutoSaveInterval); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error mesage", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg; d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedAfterAutosave) { d->autoSaveTimer.stop(); // until the next change } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, showWarnings, exportConfiguration); if (d->childSavingFuture.isCanceled()) return false; typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setAutoSaveDelay(d->autoSaveDelay); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { //qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay; d->autoSaveDelay = delay; if (isReadWrite() && d->autoSaveDelay > 0) { d->autoSaveTimer.start(d->autoSaveDelay * 1000); } else { d->autoSaveTimer.stop(); } } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); - setWindowIcon(KisIconUtils::loadIcon("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; KisMainWindow *window = KisPart::instance()->currentMainwindow(); KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified() { d->modified = true; } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; 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); } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg; d->undoStack->setUndoLimit(cfg.undoStackLimit()); setAutoSaveDelay(cfg.autoSaveInterval()); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisConfig cfg; KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); if (name != i18n("Unnamed") && !name.isEmpty()) { setUrl(QUrl::fromLocalFile(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); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList value) { d->assistants = value; } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image) { if (d->image) { // Disconnect existing sig/slot connections d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); d->image->initialRefreshGraph(); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index e419bfc485..17db104af2 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,592 +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}; 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) { ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); return result.status(); } KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); return result.status(); } QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { 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::setUpdater(KoUpdaterPtr updater) { d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@titile:window", "Open Audio")); return dialog.filename(); } KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location); } QSharedPointer filter(filterForMimeType(typeName, direction)); if (!filter) { return KisImportExportFilter::FilterCreationError; } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); if (!d->updater.isNull()) { // WARNING: The updater is not guaranteed to be persistent! If you ever want // to add progress reporting to "Save also as .kra", make sure you create // a separate KoProgressUpdater for that! // WARNING2: the failsafe completion of the updater happens in the destructor // the filter. filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, KisImportExportFilter::BadConversionGraph); ConversionResult result = KisImportExportFilter::OK; if (direction == Import) { // async importing is not yet supported! KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); if (0 && !batchMode()) { KisAsyncActionFeedback f(i18n("Opening document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); } else { result = doImport(location, filter); } } else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; if (!askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra)) { return KisImportExportFilter::UserCancelled; } if (isAsync) { result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration) { KisConfig().setExportConfiguration(typeName, exportConfiguration); } } 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/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/dialogs/kis_dlg_blacklist_cleanup.cpp b/libs/ui/dialogs/kis_dlg_blacklist_cleanup.cpp index f19e4018ec..f9f727d2f9 100644 --- a/libs/ui/dialogs/kis_dlg_blacklist_cleanup.cpp +++ b/libs/ui/dialogs/kis_dlg_blacklist_cleanup.cpp @@ -1,66 +1,66 @@ /* * * 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(); } } diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 992f5630b9..18cf5e8099 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1115 +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()); 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/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/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_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/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/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/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_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/packaging/windows/package_2.cmd b/packaging/windows/package_2.cmd index 5cbf0da2f4..978a376d55 100644 --- a/packaging/windows/package_2.cmd +++ b/packaging/windows/package_2.cmd @@ -1,331 +1,349 @@ @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 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/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..92ec3cda85 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() ); } 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/__init__.py b/plugins/extensions/pykrita/plugin/krita/__init__.py index fe5ac1a318..9021765627 100644 --- a/plugins/extensions/pykrita/plugin/krita/__init__.py +++ b/plugins/extensions/pykrita/plugin/krita/__init__.py @@ -1,72 +1,72 @@ import pykrita import os import sys import signal signal.signal(signal.SIGINT, signal.SIG_DFL) from .api import * from .decorators import * from .dockwidgetfactory import * from PyKrita import krita krita_path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, krita_path) print("%s added to PYTHONPATH" % krita_path, file=sys.stderr) # Look for PyQt try: from PyQt5 import QtCore except ImportError: print("Python cannot find the Qt5 bindings.", file=sys.stderr) print("Please make sure, that the needed packages are installed.", file=sys.stderr) raise # Shows nice looking error dialog if an unhandled exception occures. import excepthook excepthook.install() import builtins builtins.i18n = lambda s: unicode(QCoreApplication.translate("PyKrita", s)) builtins.Scripter = Krita.instance() builtins.Application = Krita.instance() builtins.Krita = Krita.instance() + def qDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.qDebug('{}: {}'.format(plugin, text)) @pykritaEventHandler('_pluginLoaded') def on_load(plugin): if plugin in init.functions: # Call registered init functions for the plugin init.fire(plugin=plugin) del init.functions[plugin] return True @pykritaEventHandler('_pluginUnloading') def on_unload(plugin): if plugin in unload.functions: # Deinitialize plugin unload.fire(plugin=plugin) del unload.functions[plugin] return True @pykritaEventHandler('_pykritaLoaded') def on_pykrita_loaded(): qDebug('PYKRITA LOADED') return True @pykritaEventHandler('_pykritaUnloading') def on_pykrita_unloading(): qDebug('UNLOADING PYKRITA') return True - diff --git a/plugins/extensions/pykrita/plugin/krita/api.py b/plugins/extensions/pykrita/plugin/krita/api.py index 66e07e5feb..410d38460c 100644 --- a/plugins/extensions/pykrita/plugin/krita/api.py +++ b/plugins/extensions/pykrita/plugin/krita/api.py @@ -1,50 +1,50 @@ # -*- coding: utf-8 -*- # Copyright (C) 2006 Paul Giannaros # Copyright (C) 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-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) version 3. # # 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. '''Provide shortcuts to access krita internals from plugins''' import contextlib import os import sys from PyKrita.krita import * import pykrita + def objectIsAlive(obj): ''' Test whether an object is alive; that is, whether the pointer to the object still exists. ''' import sip try: - sip.unwrapinstance(obj) + sip.unwrapinstance(obj) except RuntimeError: - return False + return False return True def qDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.qDebug('{}: {}'.format(plugin, text)) - diff --git a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py index eb1b3cec23..bbde02a340 100644 --- a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py +++ b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py @@ -1,438 +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), + "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()) } + 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); + 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.name, meta_property.typeName(), 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())] + 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.name, params, 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__) + 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 : + 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 : + 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/attic/scripter_hooks.py b/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py index b052bd2bbc..4ff4be1005 100644 --- a/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py +++ b/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py @@ -1,138 +1,128 @@ # -*- coding: utf-8 -*- """ -This module will be a collection of functions to hook into the GUI of Scribus. +This module will be a collection of functions to hook into the GUI of Scribus. Currently it only provides functions to add items to a menubar. Support for the toolbar, statusbar and dockarea have still to be implemented. I have to think about how to provide this stuff to QtQml. """ from PyQt5.QtWidgets import QApplication, QMenu import mikro class MenuHooks(object): + """ - This class lets extension-scripts hook into the main menu of Scribus. + This class lets extension-scripts hook into the main menu of Scribus. """ - def __init__(self, window=None): self.window = window or Scripter.dialogs.mainWindow.qt self.menubar = self.window.menuBar() self.menus = [] - def createMenu(self, title): m = QMenu(title) self.menus.append(m) self.menubar.addMenu(m) return m - def iter_menus(self): for action in self.menubar.actions(): menu = action.menu() if menu: yield menu - + def iter_inner_menus(self, menu): for action in menu.actions(): menu = action.menu() if menu: yield menu - def findMenu(self, title): """ find a menu with a given title @type title: string @param title: English title of the menu @rtype: QMenu @return: None if no menu was found, else the menu with title """ # See also http://pyqt.sourceforge.net/Docs/PyQt5/i18n.html#differences-between-pyqt5-and-qt - title = QApplication.translate(mikro.classname(self.window), title) + title = QApplication.translate(mikro.classname(self.window), title) for menu in self.iter_menus(): if menu.title() == title: return menu for innerMenu in self.iter_inner_menus(menu): if innerMenu.title() == title: return innerMenu - def actionForMenu(self, menu): for action in self.menubar.actions(): if action.menu() == menu: return action - def insertMenuBefore(self, before_menu, new_menu): """ Insert a menu after another menu in the menubar @type: before_menu QMenu instance or title string of menu @param before_menu: menu which should be after the newly inserted menu @rtype: QAction instance @return: action for inserted menu """ if isinstance(before_menu, basestring): before_menu = self.findMenu(before_menu) before_action = self.actionForMenu(before_menu) # I have no clue why QMenuBar::insertMenu only allows # to insert before another menu and not after a menu... new_action = self.menubar.insertMenu(before_action, new_menu) return new_action - def menuAfter(self, menu): # This method is needed for insertMenuAfter because - # QMenuBar.insertMenu can only insert before another menu + # QMenuBar.insertMenu can only insert before another menu previous = None for m in self.iter_menus(): if previous and previous == menu: return m previous = m - def appendMenu(self, menu): """ - Probably not that usefull + Probably not that usefull because it will add a menu after the help menu """ action = self.menubar.addMenu(menu) return action - def insertMenuAfter(self, after_menu, new_menu): """ Insert a menu before another menu in the menubar """ if isinstance(after_menu, basestring): after_menu = self.findMenu(after_menu) after_after_menu = self.menuAfter(after_menu) if after_after_menu: return self.insertMenuBefore(after_after_menu, new_menu) else: return self.appendMenu(new_menu) - def appendItem(self, menu, item, *extra_args): if isinstance(menu, basestring): title = menu menu = self.findMenu(title) if not menu: raise ValueError("Menu %r not found" % title) - if isinstance(item, QMenu): + if isinstance(item, QMenu): action = menu.addMenu(item) else: action = menu.addAction(item, *extra_args) return action - def appendSeparator(self, menu): if isinstance(menu, basestring): menu = self.findMenu(menu) menu.addSeparator() diff --git a/plugins/extensions/pykrita/plugin/krita/decorators.py b/plugins/extensions/pykrita/plugin/krita/decorators.py index b6c0789da7..57c5f432a3 100644 --- a/plugins/extensions/pykrita/plugin/krita/decorators.py +++ b/plugins/extensions/pykrita/plugin/krita/decorators.py @@ -1,109 +1,109 @@ # -*- coding: utf-8 -*- # Copyright (C) 2006 Paul Giannaros # Copyright (C) 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-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) version 3. # # 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. '''Decorators used in plugins''' import functools import inspect import sys import traceback from PyQt5 import QtCore, QtGui, QtWidgets import pykrita from .api import * # # initialization related stuff # + def pykritaEventHandler(event): def _decorator(func): setattr(pykrita, event, func) del func return _decorator def _callAll(plugin, functions, *args, **kwargs): if plugin in functions: for f in functions[plugin]: try: f(*args, **kwargs) except: traceback.print_exc() sys.stderr.write('\n') # TODO Return smth to a caller, so in case of # failed initialization it may report smth to the # C++ level and latter can show an error to the user... continue def _simpleEventListener(func): # automates the most common decorator pattern: calling a bunch # of functions when an event has occurred func.functions = dict() func.fire = functools.partial(_callAll, functions=func.functions) func.clear = func.functions.clear return func def _registerCallback(plugin, event, func): if plugin not in event.functions: event.functions[plugin] = set() event.functions[plugin].add(func) return func @_simpleEventListener def init(func): ''' The function will be called when particular plugin has loaded and the configuration has been initiated ''' plugin = sys._getframe(1).f_globals['__name__'] qDebug('@init: {}/{}'.format(plugin, func.__name__)) return _registerCallback(plugin, init, func) @_simpleEventListener def unload(func): ''' The function will be called when particular plugin is being unloaded from memory. Clean up any widgets that you have added to the interface (toolviews etc). ATTENTION Be really careful trying to access any window, view or document from the @unload handler: in case of application quit everything is dead already! ''' plugin = sys._getframe(1).f_globals['__name__'] qDebug('@unload: {}/{}'.format(plugin, func.__name__)) + def _module_cleaner(): qDebug('@unload/cleaner: {}/{}'.format(plugin, func.__name__)) if plugin in init.functions: qDebug('@unload/init-cleaner: {}/{}'.format(plugin, func.__name__)) del init.functions[plugin] func() return _registerCallback(plugin, unload, _module_cleaner) - - diff --git a/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py b/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py index 205d51b171..164785e19f 100644 --- a/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py +++ b/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py @@ -1,13 +1,14 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyKrita.krita import * + class DockWidgetFactory(DockWidgetFactoryBase): - def __init__(self, _id, _dockPosition, _klass): - super().__init__(_id, _dockPosition) - self.klass = _klass + def __init__(self, _id, _dockPosition, _klass): + super().__init__(_id, _dockPosition) + self.klass = _klass - def createDockWidget(self): - return self.klass() + def createDockWidget(self): + return self.klass() diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook.py b/plugins/extensions/pykrita/plugin/krita/excepthook.py index f5316dab5d..188422c78d 100644 --- a/plugins/extensions/pykrita/plugin/krita/excepthook.py +++ b/plugins/extensions/pykrita/plugin/krita/excepthook.py @@ -1,85 +1,81 @@ """ Exception hook If some unexpected error occures it can be shown in a nice looking dialog. Especially useful is the traceback view. Things to extend: Clicking on the filename should open an editor. Things to consider: Mail exceptions, copy to clipboard or send to bug tracker. """ import sys import cgitb import atexit from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtWidgets import QApplication, QDialog from excepthook_ui import Ui_ExceptHookDialog - def on_error(exc_type, exc_obj, exc_tb): """ This is the callback function for sys.excepthook """ dlg = ExceptHookDialog(exc_type, exc_obj, exc_tb) dlg.show() dlg.exec_() - def show_current_error(title=None): """ Call this function to show the current error. It can be used inside an except-block. """ - dlg = ExceptHookDialog(sys.exc_type, sys.exc_value, sys.exc_traceback, title) + dlg = ExceptHookDialog(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], title) dlg.show() dlg.exec_() def install(): "activates the error handler" sys.excepthook = on_error - def uninstall(): "removes the error handler" sys.excepthook = sys.__excepthook__ atexit.register(uninstall) class ExceptHookDialog(QDialog): - def __init__(self, exc_type, exc_obj, exc_tb, title=None): QDialog.__init__(self) self.ui = Ui_ExceptHookDialog() self.ui.setupUi(self) if title: self.setWindowTitle(self.windowTitle() + ": " + title) msg = "%s: %s" % (exc_type.__name__, exc_obj) self.ui.exceptionLabel.setText(msg) html = cgitb.text((exc_type, exc_obj, exc_tb)) self.ui.tracebackBrowser.setText(html) - self.resize(650, 350) # give enough space to see the backtrace better + self.resize(650, 350) # give enough space to see the backtrace better @pyqtSlot() def on_closeButton_clicked(self): self.close() if __name__ == "__main__": # Some tests: app = QApplication(sys.argv) install() print("Triggering error 1") try: fail = 1 / 0 except: show_current_error("Using inside except") print("Triggering error 2") fail2 = 1 / 0 print("This will never be reached because excepthook") print("complains about fail2") diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py b/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py index 16a80bc65f..4b68f5448f 100644 --- a/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py +++ b/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py @@ -1,48 +1,49 @@ # -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'excepthook.ui' # # Created by: PyQt5 UI code generator 5.6 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_ExceptHookDialog(object): + def setupUi(self, ExceptHookDialog): ExceptHookDialog.setObjectName("ExceptHookDialog") ExceptHookDialog.resize(542, 290) self.verticalLayout = QtWidgets.QVBoxLayout(ExceptHookDialog) self.verticalLayout.setObjectName("verticalLayout") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setSpacing(10) self.gridLayout.setObjectName("gridLayout") self.label = QtWidgets.QLabel(ExceptHookDialog) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 0, 0, 1, 1) self.exceptionLabel = QtWidgets.QLabel(ExceptHookDialog) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.exceptionLabel.setFont(font) self.exceptionLabel.setObjectName("exceptionLabel") self.gridLayout.addWidget(self.exceptionLabel, 1, 0, 1, 1) self.verticalLayout.addLayout(self.gridLayout) self.tracebackBrowser = QtWidgets.QTextBrowser(ExceptHookDialog) self.tracebackBrowser.setMinimumSize(QtCore.QSize(0, 200)) self.tracebackBrowser.setObjectName("tracebackBrowser") self.verticalLayout.addWidget(self.tracebackBrowser) self.closeButton = QtWidgets.QPushButton(ExceptHookDialog) self.closeButton.setObjectName("closeButton") self.verticalLayout.addWidget(self.closeButton) self.retranslateUi(ExceptHookDialog) QtCore.QMetaObject.connectSlotsByName(ExceptHookDialog) def retranslateUi(self, ExceptHookDialog): _translate = QtCore.QCoreApplication.translate ExceptHookDialog.setWindowTitle(_translate("ExceptHookDialog", "Script error")) self.label.setText(_translate("ExceptHookDialog", "An exception occurred while running the script.")) self.exceptionLabel.setText(_translate("ExceptHookDialog", "Exception")) self.closeButton.setText(_translate("ExceptHookDialog", "&Close")) - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py b/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py index 48c0e362a6..77cb2b44b8 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- editor_main_window = None def launch(parent=None): global editor_main_window if not editor_main_window: from sceditor.mainwindow import EditorMainWindow editor_main_window = EditorMainWindow(parent) - editor_main_window.resize(640,480) + editor_main_window.resize(640, 480) editor_main_window.show() - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py b/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py index e524bcbbd6..21f6e942d6 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py @@ -1,155 +1,136 @@ from PyQt5.QtCore import QTimer, Qt from PyQt5.QtWidgets import (qApp, QListWidget, QListWidgetItem, QTextBrowser, QVBoxLayout, QWidget) class PopupWidget(QWidget): - def __init__(self, textedit): flags = Qt.ToolTip flags = Qt.Window | Qt.FramelessWindowHint | \ - Qt.CustomizeWindowHint | Qt.X11BypassWindowManagerHint + Qt.CustomizeWindowHint | Qt.X11BypassWindowManagerHint QWidget.__init__(self, None, flags) self.textedit = textedit self.vlayout = QVBoxLayout(self) self.vlayout.setContentsMargins(0, 0, 0, 0) self.init_popup() self.show() self.hide() self.active = False - def show(self, timeout=0, above=False): self.cursor_start_col = self.textedit.textCursor().columnNumber() desktop = qApp.desktop() screen = desktop.screen(desktop.screenNumber(self)) screen_width = screen.width() screen_height = screen.height() win_width = self.width() win_height = self.height() cursorRect = self.textedit.cursorRect() if above: pos = self.textedit.mapToGlobal(cursorRect.topLeft()) pos.setY(pos.y() - win_height) else: pos = self.textedit.mapToGlobal(cursorRect.bottomLeft()) if pos.y() < 0: pos = self.textedit.mapToGlobal(cursorRect.bottomLeft()) if pos.y() + win_height > screen_height: pos = self.textedit.mapToGlobal(cursorRect.topLeft()) pos.setY(pos.y() - win_height) if pos.x() + win_width > screen_width: pos.setX(screen_width - win_width) self.move(pos) QWidget.show(self) self.active = True if timeout: QTimer.singleShot(timeout * 1000, self.hide) - def hide(self): self.active = False QWidget.hide(self) - - - - class CallTip(PopupWidget): - def init_popup(self): self.browser = QTextBrowser(self) self.layout().addWidget(self.browser) - - class AutoCompleteItem(QListWidgetItem): def __init__(self, item): QListWidgetItem.__init__(self) value = item.name self.setText(value) self.value = value self.kind = item.kind - class AutoComplete(PopupWidget): - def init_popup(self): self.list = QListWidget(self) self.list.itemClicked.connect(self.insertItem) self.layout().addWidget(self.list) self.items = [] - def insertItem(self, item): self.insert() - def insert(self): completition = self.items[self.list.currentRow()].value cursor = self.textedit.textCursor() col = cursor.columnNumber() line = unicode(cursor.block().text()) i = self.cursor_start_col while i > 0: - #print(`line[i:col]`) + # print(`line[i:col]`) if completition.startswith(line[i:col]): - #print("break") + # print("break") break i -= 1 - #print(col,i) - cursor.insertText(completition[col-i:]) + # print(col,i) + cursor.insertText(completition[col - i:]) self.hide() - def setItems(self, proposals): - proposals = sorted(proposals, cmp=lambda p1,p2:cmp(p1.name,p2.name)) + proposals = sorted(proposals, cmp=lambda p1, p2: cmp(p1.name, p2.name)) del self.items[:] self.list.clear() for entry in proposals: i = AutoCompleteItem(entry) self.list.addItem(i) self.items.append(i) - def keyPressEvent(self, event): self.list.keyPressEvent(event) key = event.key() text = event.text() if key in [Qt.Key_Right, Qt.Key_Enter, Qt.Key_Return]: text = "" cursor = self.textedit.textCursor() line = unicode(cursor.block().text()) col = cursor.columnNumber() prefix = line[self.cursor_start_col:col] + unicode(text) found = False for row, item in enumerate(self.items): if item.value.startswith(prefix): current = self.items[self.list.currentRow()].value if not current.startswith(prefix): self.list.setCurrentRow(row) found = True break if not found: self.hide() return if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown]: return True elif key in [Qt.Key_Tab, Qt.Key_Right, Qt.Key_Enter, Qt.Key_Return]: self.insert() return True elif not text: self.hide() - - - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py index 7b4010aad2..adc88aa00b 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py @@ -1,524 +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:] + 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: + 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 + locals = self.locals code = compile(source, self.name, "exec") try: - exec code in locals + exec(code, locals) except: - self.showtraceback() + self.showtraceback() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass - + except: + pass def push(self, line): if self.lines: if line: self.lines.append(line) - return 1 # want more! + 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! + return 1 # want more! else: self.showtraceback() except: self.showtraceback() else: try: - exec code in self.locals + exec(code, self.locals) except: self.showtraceback() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass + except: + pass return 0 - def showtraceback(self): self.lines = [] - if sys.exc_type == SyntaxError: # and len(sys.exc_value) == 2: + 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_type) + ":", sys.exc_value[0]) + print(str(sys.exc_info()[0]) + ":", sys.exc_value[0]) else: - traceback.print_tb(sys.exc_traceback, None) - print(sys.exc_type.__name__ + ":", sys.exc_value) - - + 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, __builtin__ + 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__'): + 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__'): + 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") + # 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) - - + 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 + 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', + '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'] - 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 + 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 = 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 2134fd08e3..ded5039429 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py @@ -1,428 +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 + 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 + \ + QPoint(fw, fw + mw + self.collapseButton.size().height() + self.pinButton.size().height()), QSize( - self.geometry().width() - (fw * 2), - self.geometry().height() - (fw * 2) - \ + self.geometry().width() - (fw * 2), + 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 + \ + QPoint(fw + mw + self.collapseButton.size().width() + self.pinButton.size().width(), fw), QSize( - self.geometry().width() - (fw * 2) - \ + self.geometry().width() - (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)), + QPoint(fh, 40), # self.geometry().height() - (fh * 3)), QSize( - self.geometry().width() - (fh * 2), + self.geometry().width() - (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) + 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().width() - (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)) - + 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: + 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) + 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/dockwidget_icons.py b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py index ce9861f796..9da7eb289a 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py @@ -1,154 +1,156 @@ # -*- coding: utf-8 -*- # Resource object code # # Created: Mi Aug 20 05:23:34 2008 # by: The Resource Compiler for PyQt (Qt v4.3.4) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = "\ \x00\x00\x02\x0d\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ \x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8a\x49\x44\ \x41\x54\x78\xda\xdd\x93\x31\x4b\x5c\x41\x14\x85\xcf\xbc\x37\xc3\ \x6e\x50\x57\xc5\x64\xc5\x3f\x91\xca\x56\x48\x13\x42\x04\x8d\x8b\ \x55\xfe\x80\xe8\x3f\x90\x20\x68\x61\x93\xc2\xc2\x5a\xd1\x36\x65\ \x92\x26\x16\x46\xc5\x22\xe5\x6a\x23\x08\x8b\x98\x14\xba\xb3\xef\ \xe5\xed\x2a\x6f\x67\x57\x9d\xf7\xf6\x38\x3c\xd0\xca\x28\x92\x42\ \xf0\xbb\x5c\xb8\x30\x70\x38\x9c\x3b\x57\x90\xc4\xff\xe0\xb9\x7e\ \x5a\x01\x09\x87\x70\x60\x02\x79\x3c\x86\xaf\xb8\xa0\x23\x13\xc0\ \x18\x16\x47\x87\x47\x3f\x59\x61\x61\x5c\xc5\x7e\x0c\xe3\xbb\x49\ \x1a\xd4\x44\x00\x14\x00\x74\x89\xcc\x6f\x91\x45\xf4\x5e\xf6\xa1\ \x32\x52\x59\x01\x30\x95\x09\xc0\x60\x49\xff\xd5\xd3\xa5\x52\xa9\ \x5f\x9f\x6b\x51\x35\x55\x68\xab\x11\x7a\x21\x6a\xac\x01\xaf\x00\ \xbc\x64\xe6\xb7\xa1\x23\xe2\xb7\x67\x70\x95\xce\xdf\x66\xc0\x9f\ \xac\x97\x0f\xcb\x6f\xb7\x76\xb6\xac\x92\x0a\xca\x53\x90\x42\x42\ \x75\x14\x70\x05\xa0\xe5\xba\xee\x3a\x00\x06\x6a\xc5\x34\x08\x83\ \x12\x67\x59\xbd\xc9\x20\x83\x3f\x58\x16\x1f\xc4\x74\x77\x6f\xf7\ \x4a\x61\xa8\xe0\xab\x54\xc1\x4f\x7d\x78\x4d\x0f\x1d\x57\x68\x3b\ \x23\x76\x30\x0d\x8e\xc3\x39\x2e\xd8\xcd\x3b\xb7\xc0\x6f\x5c\xdf\ \xd8\xdd\x58\x8d\xeb\x71\x2a\x13\x09\xaf\xed\x41\x9e\x4b\x20\x02\ \x7a\xa2\x42\xd2\xfc\x63\xbe\xa7\x0b\x97\x9f\xef\x5d\xa3\x7d\x6d\ \x67\xb6\x7f\x6d\xef\xb1\xc5\x54\x5d\x28\xb0\x4e\xe4\xce\x72\x49\ \x27\xe2\x51\x9b\xcd\xc9\x07\xff\x01\xe7\xc9\xf8\x24\x7e\xb7\x77\ \xb0\xdf\x90\x2d\x49\x46\xa4\x4a\x72\xc6\x34\xe2\x37\x74\x6f\x0f\ \x0a\xdc\x84\x1a\x06\xc1\xfb\x13\x7d\x6a\x73\x2a\x9f\x34\x1b\xad\ \x71\x2e\x53\xe3\x0e\xee\xbd\x05\xf1\x51\x4c\xe2\x85\xaf\xb8\x96\ \x7c\xc1\x3f\x78\x06\xc7\x74\x0d\x90\x24\xc3\xdb\x6d\x74\x09\xd1\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x6c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\ \xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x12\x00\x00\ \x0b\x12\x01\xd2\xdd\x7e\xfc\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ \xd2\x0b\x01\x0d\x00\x32\x9c\x41\x83\x23\x00\x00\x01\xe9\x49\x44\ \x41\x54\x78\xda\x85\x93\xbd\x8b\x13\x51\x14\xc5\x7f\x4f\x07\xd7\ \xc4\x04\xb2\x90\x2d\x32\x33\x11\x41\x10\x54\x58\x85\xf8\x0f\xd8\ \x5a\x6a\x61\x65\x63\x21\x06\x02\x3a\x29\xd2\xd8\x29\x62\x13\xc2\ \x42\x94\x34\x61\x43\x48\x1a\x21\x56\xdb\xd8\x68\x65\xa7\x29\x76\ \x41\x50\x41\x0c\x64\x87\xc9\x84\x7c\x1a\x21\xb3\x60\x66\x2c\xb2\ \x6f\x36\x93\x1d\xf0\xc1\x2b\xde\xfd\x38\xe7\xde\x7b\xde\x15\xdd\ \x6e\x17\x00\x21\x04\x42\x08\x34\x4d\xc3\xb2\x2c\xff\xbd\x7a\x57\ \xe3\xe4\x55\x9a\xcd\x66\xc0\x09\x90\x4a\xa5\x00\xe8\x74\x3a\xb4\ \x5a\xad\x80\x6f\x15\xc4\x30\x0c\x84\x69\x9a\x01\x06\x99\x2c\xcf\ \x60\x30\x08\xad\x22\x91\x48\x00\x2c\x2b\x58\x67\x00\x3c\x49\x96\ \x4c\x26\x01\x28\x95\x4a\x7e\x8c\x61\x18\xbe\x1f\xcb\xb2\xe8\xf5\ \x7a\xd8\xb6\x4d\xbf\xdf\xf7\x01\xf6\x5f\xc7\xbc\x63\xa0\x8c\x34\ \x66\x32\x99\x75\x1f\x4a\xa3\xd1\x08\xf4\x25\x4f\x64\xeb\x26\x3f\ \xde\xc2\x95\xfb\x9f\xbe\xc8\xe4\x76\xbb\xcd\xbb\x67\x11\x62\xfa\ \x6d\x60\x6f\xd9\x42\xa1\x50\xe0\x98\xe5\x0e\xf0\x5c\x02\x6c\x24\ \xb7\xb9\x90\xba\xca\x87\x37\x2e\x49\xef\x80\x98\xae\xf2\xe7\xf0\ \x3b\x03\xb1\x8d\x12\x4f\x9f\x0c\x14\xa8\x00\x8f\x4d\xd3\x44\x55\ \x55\x4c\xd3\x44\xd7\x75\x66\x87\x7b\xb8\x7f\x67\x98\xfd\x23\xd4\ \xcd\xdf\x28\x91\x34\x9e\x7b\xc4\xfe\xd7\x2e\x37\xae\xa7\xb9\x7c\ \xeb\x09\x80\x50\x80\x2c\x50\xd5\x34\x2d\x50\xc1\xcf\xcf\x35\x46\ \xdf\xde\x73\xf0\x6b\x41\x7a\xeb\x0c\x00\x9b\x31\x41\xc7\x76\xb9\ \x14\xb9\xeb\xcf\x4b\x14\x8b\xc5\x80\x3c\xf9\x7c\x1e\xc0\xfb\xf8\ \x2a\x4a\xc7\x76\x79\xb8\xe3\x04\x64\x7d\xf1\xe0\x1c\xd7\x2e\x9e\ \xe5\xde\xcb\xf9\xb2\x83\xe1\x70\xc8\x68\x34\x62\x3c\x1e\x33\x99\ \x4c\x7c\x15\x76\x9f\x9e\x3f\xa5\x82\xf4\xed\x3c\xda\x38\x51\xa1\ \x5e\xaf\x87\xfd\x03\xc9\xec\x1b\xcb\xe5\xb2\x8c\x13\xb9\x5c\xce\ \xf3\x87\x38\x9d\x4e\x03\x2d\xc4\xe3\xf1\x00\x90\xe3\x38\xa1\x3f\ \x51\x51\x94\xa5\x8c\xb5\x5a\x2d\xb4\x02\x00\xcb\xb2\xa8\x56\xab\ \xa7\x76\x05\xa0\x52\xa9\x90\xcd\x66\x11\xb3\xd9\x2c\xc0\x10\x8d\ \x46\x99\xcf\xe7\xa1\xdb\xb8\x5e\x09\x80\x70\x1c\x87\xc5\x62\xf1\ \xdf\xb5\x0d\x4b\x06\xf8\x07\xf0\x1d\xb1\x3d\x6a\xe9\x1c\x20\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x0f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ \x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8c\x49\x44\ \x41\x54\x78\xda\xa5\x93\x3f\x4b\x9b\x51\x14\x87\x7f\xe7\xfe\x79\ \x93\x5c\xd3\x94\x1a\xad\x28\x2e\x9a\x21\x38\x04\x6a\xa1\x85\x40\ \x1d\x05\xc9\xa4\x11\xa1\x54\x27\x33\xb4\x73\xa5\x73\xe9\x26\x01\ \xc1\xc5\x45\x17\xe9\xd2\x51\xfc\x16\x0e\xfd\x02\x8d\x58\x11\x41\ \x13\x93\x9a\x28\xc6\x44\xdf\x7b\xdf\xa3\x59\x2d\x89\xd1\x3c\x70\ \xa6\x03\x0f\x9c\xdf\x39\x87\x98\x19\xbd\x20\xf0\x00\x9a\xa5\x4c\ \x64\x3e\xb2\xf4\x6c\x01\x18\x4b\x2b\x8b\x2b\x3f\x63\x1f\x63\x5b\ \xf4\x83\xe8\xc9\x82\x16\x66\xc0\x50\x6e\x21\xb7\x3c\x7c\x38\xfc\ \x9b\xa6\xa9\xff\x69\x82\x08\x50\xbc\x2c\xc2\x85\x9d\xc8\xce\x66\ \xdf\x24\x92\x89\x3f\x94\xa1\x49\xb4\x41\xe1\x21\x7d\x40\xb1\x5e\ \x04\xa8\xd5\x54\x22\xfd\x21\x1d\x37\x2f\xcc\x1e\xcd\xd1\x67\xde\ \xe1\xed\x47\x05\xc2\x48\x59\xb2\x25\x04\x41\xd0\x12\x40\x5b\x4d\ \xa3\xe3\xa3\x9e\x0e\xe9\x2d\xef\x93\x97\xf6\x93\xfe\x17\xfe\xce\ \xdc\x5e\x10\x16\xb2\x42\x15\xf8\xe4\x43\x3b\x0d\xe5\x2b\xc8\x86\ \x84\x8e\x68\x95\x1a\x4b\xe5\x0a\xc7\xfb\x6f\x89\xe8\x3d\xdf\xd3\ \x56\x50\xa0\x02\x6c\x60\x81\x06\x40\x17\x04\x51\x13\x70\x65\x07\ \x73\x63\x38\x30\x01\x75\x0c\x51\x7a\x5a\x88\x41\x01\xc4\x01\xbc\ \x04\x38\xca\x70\x21\x87\x57\x43\xfd\xd6\xc6\x83\x5f\xcd\x91\xe6\ \x3b\xe6\x4e\x23\x78\x42\x71\x4b\x70\x05\xc0\x02\xba\xae\xd9\x67\ \xe7\x57\x9b\xb5\xaf\xbc\xee\x36\x1e\x0d\x91\x34\xc9\xfb\x02\x08\ \x88\x22\xea\xc2\x0d\x73\x55\x69\x54\x67\x78\xed\x76\xaf\xab\x35\ \x4a\xad\x24\x9f\x5a\xbc\xae\x0f\x59\x7b\x14\x1c\x54\x4e\xce\xa6\ \x38\xcf\xe5\xae\x0f\x49\x28\x21\xc7\xfe\x25\x70\xfd\xf7\x76\xf7\ \xbc\x5a\x9e\xe0\x55\x2e\xa3\x03\xff\x7d\xa3\xca\x87\xe6\x40\x34\ \x68\xbf\x35\x37\xd1\x05\x3d\xbf\xf3\x1d\x7f\x4b\x95\x33\x4b\xa1\ \xe2\xc5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ " qt_resource_name = "\ \x00\x0e\ \x06\x0c\x0a\x07\ \x00\x61\ \x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x64\x00\x6f\x00\x77\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x07\ \x07\x01\x57\xa7\ \x00\x70\ \x00\x69\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x0f\x22\x64\xc7\ \x00\x61\ \x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x72\x00\x69\x00\x67\x00\x68\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x02\x11\ \x00\x00\x00\x36\x00\x00\x00\x00\x00\x01\x00\x00\x04\x81\ " + 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/krita/sceditor/highlighter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py index 9e66a1cb5f..e1b34e07f3 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py @@ -1,193 +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())) + startIndex + len(self.multiLineStringBegin.pattern())) if endIndex == -1: self.setCurrentBlockState(1) commentLength = text.length() - startIndex else: commentLength = endIndex - startIndex + \ - self.multiLineStringEnd.matchedLength() + self.multiLineStringEnd.matchedLength() self.highlightRules(text, endIndex, len(text)) - + self.setFormat(startIndex, commentLength, self.multiLineStringFormat) startIndex = self.multiLineStringBegin.indexIn(text, - startIndex + commentLength) - + 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.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.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.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))) + 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.rules += map(lambda s: (QtCore.QRegExp(r"\b" + s + r"\b"), 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', + ['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 __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.rules += map(lambda s: (QtCore.QRegExp(r"\b" + s + r"\b"), 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"\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/indenter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py index bd635ddfef..593773bbdb 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py @@ -1,242 +1,244 @@ import re from rope.base import codeanalyze class TextIndenter(object): + """A class for formatting texts""" def __init__(self, editor, indents=4): self.editor = editor self.indents = indents self.line_editor = editor.line_editor() def correct_indentation(self, lineno): """Correct the indentation of a line""" def deindent(self, lineno): """Deindent the a line""" current_indents = self._count_line_indents(lineno) new_indents = max(0, current_indents - self.indents) self._set_line_indents(lineno, new_indents) def indent(self, lineno): """Indent a line""" current_indents = self._count_line_indents(lineno) new_indents = current_indents + self.indents self._set_line_indents(lineno, new_indents) def entering_new_line(self, lineno): """Indent a line Uses `correct_indentation` and last line indents """ last_line = "" if lineno > 1: last_line = self.line_editor.get_line(lineno - 1) if last_line.strip() == '': self._set_line_indents(lineno, len(last_line)) else: self.correct_indentation(lineno) def insert_tab(self, index): """Inserts a tab in the given index""" self.editor.insert(index, ' ' * self.indents) def _set_line_indents(self, lineno, indents): old_indents = self._count_line_indents(lineno) indent_diffs = indents - old_indents self.line_editor.indent_line(lineno, indent_diffs) def _count_line_indents(self, lineno): contents = self.line_editor.get_line(lineno) result = 0 for x in contents: if x == ' ': result += 1 elif x == '\t': result += 8 else: break return result class NormalIndenter(TextIndenter): def __init__(self, editor): super(NormalIndenter, self).__init__(editor) def correct_indentation(self, lineno): prev_indents = 0 if lineno > 1: prev_indents = self._count_line_indents(lineno - 1) self._set_line_indents(lineno, prev_indents) class PythonCodeIndenter(TextIndenter): def __init__(self, editor, indents=4): super(PythonCodeIndenter, self).__init__(editor, indents) def _last_non_blank(self, lineno): current_line = lineno - 1 while current_line != 1 and \ - self.line_editor.get_line(current_line).strip() == '': + self.line_editor.get_line(current_line).strip() == '': current_line -= 1 return current_line def _get_correct_indentation(self, lineno): if lineno == 1: return 0 new_indent = self._get_base_indentation(lineno) prev_lineno = self._last_non_blank(lineno) prev_line = self.line_editor.get_line(prev_lineno) if prev_lineno == lineno or prev_line.strip() == '': new_indent = 0 current_line = self.line_editor.get_line(lineno) new_indent += self._indents_caused_by_current_stmt(current_line) return new_indent def _get_base_indentation(self, lineno): range_finder = _StatementRangeFinder( self.line_editor, self._last_non_blank(lineno)) start = range_finder.get_statement_start() if not range_finder.is_line_continued(): changes = self._indents_caused_by_prev_stmt( (start, self._last_non_blank(lineno))) return self._count_line_indents(start) + changes if range_finder.last_open_parens(): open_parens = range_finder.last_open_parens() parens_line = self.line_editor.get_line(open_parens[0]) if parens_line[open_parens[1] + 1:].strip() == '': if len(range_finder.open_parens) > 1: return range_finder.open_parens[-2][1] + 1 else: return self._count_line_indents(start) + self.indents return range_finder.last_open_parens()[1] + 1 start_line = self.line_editor.get_line(start) if start == lineno - 1: try: equals_index = start_line.index(' = ') + 1 if start_line[equals_index + 1:].strip() == '\\': return self._count_line_indents(start) + self.indents return equals_index + 2 except ValueError: match = re.search(r'(\b )|(\.)', start_line) if match: return match.start() + 1 else: return len(start_line) + 1 else: return self._count_line_indents(self._last_non_blank(lineno)) def _indents_caused_by_prev_stmt(self, stmt_range): first_line = self.line_editor.get_line(stmt_range[0]) last_line = self.line_editor.get_line(stmt_range[1]) new_indent = 0 if self._strip(last_line).endswith(':'): new_indent += self.indents if self._startswith(first_line, ('return', 'raise', 'pass', 'break', 'continue')): new_indent -= self.indents return new_indent def _startswith(self, line, tokens): line = self._strip(line) for token in tokens: if line == token or line.startswith(token + ' '): return True def _strip(self, line): try: numsign = line.rindex('#') comment = line[numsign:] if '\'' not in comment and '\"' not in comment: line = line[:numsign] except ValueError: pass return line.strip() def _indents_caused_by_current_stmt(self, current_line): new_indent = 0 if self._strip(current_line) == 'else:': new_indent -= self.indents if self._strip(current_line) == 'finally:': new_indent -= self.indents if self._startswith(current_line, ('elif',)): new_indent -= self.indents if self._startswith(current_line, ('except',)) and \ self._strip(current_line).endswith(':'): new_indent -= self.indents return new_indent def correct_indentation(self, lineno): """Correct the indentation of the line containing the given index""" self._set_line_indents(lineno, self._get_correct_indentation(lineno)) class _StatementRangeFinder(object): + """A method object for finding the range of a statement""" def __init__(self, lines, lineno): self.lines = lines self.lineno = lineno self.in_string = '' self.open_count = 0 self.explicit_continuation = False self.open_parens = [] self._analyze() def _analyze_line(self, lineno): current_line = self.lines.get_line(lineno) for i, char in enumerate(current_line): if char in '\'"': if self.in_string == '': self.in_string = char if char * 3 == current_line[i:i + 3]: self.in_string = char * 3 elif self.in_string == current_line[i:i + len(self.in_string)] and \ - not (i > 0 and current_line[i - 1] == '\\' and - not (i > 1 and current_line[i - 2:i] == '\\\\')): + not (i > 0 and current_line[i - 1] == '\\' and + not (i > 1 and current_line[i - 2:i] == '\\\\')): self.in_string = '' if self.in_string != '': continue if char == '#': break if char in '([{': self.open_count += 1 self.open_parens.append((lineno, i)) if char in ')]}': self.open_count -= 1 if self.open_parens: self.open_parens.pop() if current_line and char != '#' and current_line.endswith('\\'): self.explicit_continuation = True else: self.explicit_continuation = False def _analyze(self): last_statement = 1 block_start = codeanalyze.get_block_start(self.lines, self.lineno) for current_line_number in range(block_start, self.lineno + 1): if not self.explicit_continuation and \ self.open_count == 0 and self.in_string == '': last_statement = current_line_number self._analyze_line(current_line_number) self.statement_start = last_statement def get_statement_start(self): return self.statement_start def last_open_parens(self): if not self.open_parens: return None return self.open_parens[-1] def is_line_continued(self): return self.open_count != 0 or self.explicit_continuation def get_line_indents(self, line_number): return self._count_line_indents(self.lines.get_line(line_number)) diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py index 8912a8b7e9..47f9bf8e80 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py @@ -1,220 +1,207 @@ from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtGui import QCloseEvent from PyQt5.QtWidgets import (QApplication, QFileDialog, QMainWindow, QMessageBox, QSplitter, QTabWidget) from widget import PythonEditorWidget, QtQmlEditorWidget, SaveDialog from console import PythonConsole, QtQmlConsole from mainwindow_ui import Ui_ScriptEditor import traceback import os template_py = """\ # -*- coding: utf-8 -*- from __future__ import with_statement """ -class EditorMainWindow(QMainWindow): +class EditorMainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.ui = Ui_ScriptEditor() self.ui.setupUi(self) - #self.ui.actionExit.triggered.connect(self.exit) + # self.ui.actionExit.triggered.connect(self.exit) self.splitter = QSplitter(Qt.Vertical, self) self.setCentralWidget(self.splitter) self.edit_tab = QTabWidget(self.splitter) self.console_tab = QTabWidget(self.splitter) self.py_console = PythonConsole(self.console_tab) self.console_tab.addTab(self.py_console, "&Python console") self.js_console = QtQmlConsole(self.console_tab) self.console_tab.addTab(self.js_console, "&QtQml console") self.editors = [] self.on_actionNewPython_triggered() @pyqtSlot() def closeEvent(self, event): - while(self.editors.__len__()): - edit = self.edit_tab.currentWidget() + while(self.editors.__len__()): + edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("You have unsaved script. Save it now?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - event.ignore() - self.save(True) - elif(prompt == QMessageBox.Cancel): - event.ignore() - return - elif(prompt == QMessageBox.Discard): - event.accept() + if(edit.isModified()): + saveBox = SaveDialog("You have unsaved script. Save it now?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + event.ignore() + self.save(True) + elif(prompt == QMessageBox.Cancel): + event.ignore() + return + elif(prompt == QMessageBox.Discard): + event.accept() i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - event.accept() - - + event.accept() @pyqtSlot() def on_actionExit_triggered(self): - while(self.editors.__len__()): - edit = self.edit_tab.currentWidget() + while(self.editors.__len__()): + edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("You have unsaved script. Save it now?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - self.save(True) - elif(prompt == QMessageBox.Cancel): - return - elif(prompt == QMessageBox.Discard): - pass - i = self.edit_tab.indexOf(edit) + if(edit.isModified()): + saveBox = SaveDialog("You have unsaved script. Save it now?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + self.save(True) + elif(prompt == QMessageBox.Cancel): + return + elif(prompt == QMessageBox.Discard): + pass + i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - self.close() + self.close() @pyqtSlot() def on_actionNewPython_triggered(self): pyedit = PythonEditorWidget(self.edit_tab) pyedit.setPlainText(template_py) self.edit_tab.addTab(pyedit, "Python") self.edit_tab.setCurrentWidget(pyedit) self.editors.append(pyedit) self.py_console.attach() self.console_tab.setCurrentIndex(0) pyedit.setFocus() pyedit.view.setFocus() - @pyqtSlot() def on_actionNewQtQml_triggered(self): jsedit = QtQmlEditorWidget(self.edit_tab) self.edit_tab.addTab(jsedit, "QtQml") self.edit_tab.setCurrentWidget(jsedit) self.editors.append(jsedit) self.js_console.attach() self.console_tab.setCurrentIndex(1) - @pyqtSlot() def on_actionClose_triggered(self): edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("Do you want to save this Script?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - self.save(True) - elif(prompt == QMessageBox.Cancel): - return - elif(prompt == QMessageBox.Discard): - pass + if(edit.isModified()): + saveBox = SaveDialog("Do you want to save this Script?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + self.save(True) + elif(prompt == QMessageBox.Cancel): + return + elif(prompt == QMessageBox.Discard): + pass i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - @pyqtSlot() def on_actionClear_triggered(self): - #edit = self.edit_tab.currentWidget() - #edit.setPlainText(template_py) - self.py_console.clear() - + # edit = self.edit_tab.currentWidget() + # edit.setPlainText(template_py) + self.py_console.clear() @pyqtSlot() def on_actionSave_As_triggered(self): - self.save() - + self.save() @pyqtSlot() def on_actionSave_triggered(self): - self.save(True) + self.save(True) - - #Path of the script file in each tab will be stored in tabToolTip - def save(self, Update = False): + # Path of the script file in each tab will be stored in tabToolTip + def save(self, Update=False): edit = self.edit_tab.currentWidget() - contents = str(edit.toPlainText()) - if((Update == False) or (self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python") ): - #Save in its first invocation and Save As will enter - filename = QFileDialog.getSaveFileName(self, "Save File", "", "*.spy") - fil = open(filename , 'w') - if(filename and self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python"): - #Script hasn't been saved before and user specifies a valid filename - self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename+'.spy') - self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename+'.spy'))) - else: - #filename = self.edit_tab.tabText(self.edit_tab.currentIndex()) - filename = self.edit_tab.tabToolTip(self.edit_tab.currentIndex()) - fil = open( filename , 'w') - fil.write(contents) - fil.close() - edit.setModified(False) - + contents = str(edit.toPlainText()) + if((Update == False) or (self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python")): + # Save in its first invocation and Save As will enter + filename = QFileDialog.getSaveFileName(self, "Save File", "", "*.spy") + fil = open(filename, 'w') + if(filename and self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python"): + # Script hasn't been saved before and user specifies a valid filename + self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename + '.spy') + self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename + '.spy'))) + else: + # filename = self.edit_tab.tabText(self.edit_tab.currentIndex()) + filename = self.edit_tab.tabToolTip(self.edit_tab.currentIndex()) + fil = open(filename, 'w') + fil.write(contents) + fil.close() + edit.setModified(False) @pyqtSlot() def on_actionOpen_triggered(self): - filename = QFileDialog.getOpenFileName(self,"Open File","","*.spy") - try: - fil = open(filename , 'r') - except IOError: - return - code = fil.read() - edit = self.edit_tab.currentWidget() - self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename))) - self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename) - edit.setPlainText(code) - fil.close() - + filename = QFileDialog.getOpenFileName(self, "Open File", "", "*.spy") + try: + fil = open(filename, 'r') + except IOError: + return + code = fil.read() + edit = self.edit_tab.currentWidget() + self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename))) + self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename) + edit.setPlainText(code) + fil.close() @pyqtSlot() def on_actionRun_triggered(self): self.run() - @pyqtSlot() def on_actionRunConsole_triggered(self): self.run(True) - def run(self, console=False): edit = self.edit_tab.currentWidget() code = str(edit.toPlainText()) if isinstance(edit, PythonEditorWidget): self.py_console.attach() self.console_tab.setCurrentIndex(0) if console: namespace = self.py_console.namespace else: namespace = {} try: - exec code in namespace + exec(code, namespace) except Exception as e: traceback.print_exc() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass + except: + pass else: self.js_console.attach() self.console_tab.setCurrentIndex(1) if console: self.js_console.inter.execute(code) else: self.js_console.inter.execute_code(code) - - if __name__ == "__main__": import sys app = QApplication(sys.argv) win = EditorMainWindow() win.resize(640, 480) win.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py index 6e52bcf3f7..1fff05218a 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py @@ -1,97 +1,98 @@ # -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'mainwindow.ui' # # Created: Fri Aug 15 04:25:57 2014 # by: PyQt5 UI code generator 5.3.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_ScriptEditor(object): + def setupUi(self, ScriptEditor): ScriptEditor.setObjectName("ScriptEditor") ScriptEditor.resize(624, 449) self.centralwidget = QtWidgets.QWidget(ScriptEditor) self.centralwidget.setObjectName("centralwidget") ScriptEditor.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(ScriptEditor) self.menubar.setGeometry(QtCore.QRect(0, 0, 624, 25)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") self.menu_New = QtWidgets.QMenu(self.menuFile) self.menu_New.setObjectName("menu_New") self.menuRun = QtWidgets.QMenu(self.menubar) self.menuRun.setObjectName("menuRun") ScriptEditor.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(ScriptEditor) self.statusbar.setObjectName("statusbar") ScriptEditor.setStatusBar(self.statusbar) self.actionClose = QtWidgets.QAction(ScriptEditor) self.actionClose.setObjectName("actionClose") self.actionExit = QtWidgets.QAction(ScriptEditor) self.actionExit.setObjectName("actionExit") self.actionRun = QtWidgets.QAction(ScriptEditor) self.actionRun.setObjectName("actionRun") self.actionRunConsole = QtWidgets.QAction(ScriptEditor) self.actionRunConsole.setObjectName("actionRunConsole") self.actionNewPython = QtWidgets.QAction(ScriptEditor) self.actionNewPython.setObjectName("actionNewPython") self.actionNewQtQml = QtWidgets.QAction(ScriptEditor) self.actionNewQtQml.setObjectName("actionNewQtQml") self.actionClear = QtWidgets.QAction(ScriptEditor) self.actionClear.setObjectName("actionClear") self.actionSave_As = QtWidgets.QAction(ScriptEditor) self.actionSave_As.setObjectName("actionSave_As") self.actionOpen = QtWidgets.QAction(ScriptEditor) self.actionOpen.setObjectName("actionOpen") self.actionSave = QtWidgets.QAction(ScriptEditor) self.actionSave.setObjectName("actionSave") self.menu_New.addAction(self.actionNewPython) self.menu_New.addAction(self.actionNewQtQml) self.menuFile.addAction(self.menu_New.menuAction()) self.menuFile.addAction(self.actionOpen) self.menuFile.addAction(self.actionSave) self.menuFile.addAction(self.actionSave_As) self.menuFile.addAction(self.actionClose) self.menuFile.addSeparator() self.menuFile.addAction(self.actionExit) self.menuRun.addAction(self.actionRun) self.menuRun.addAction(self.actionRunConsole) self.menuRun.addAction(self.actionClear) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuRun.menuAction()) self.retranslateUi(ScriptEditor) QtCore.QMetaObject.connectSlotsByName(ScriptEditor) def retranslateUi(self, ScriptEditor): _translate = QtCore.QCoreApplication.translate ScriptEditor.setWindowTitle(_translate("ScriptEditor", "Script Editor")) self.menuFile.setTitle(_translate("ScriptEditor", "&File")) self.menu_New.setTitle(_translate("ScriptEditor", "&New")) self.menuRun.setTitle(_translate("ScriptEditor", "&Run")) self.actionClose.setText(_translate("ScriptEditor", "&Close")) self.actionClose.setShortcut(_translate("ScriptEditor", "Ctrl+W")) self.actionExit.setText(_translate("ScriptEditor", "&Exit")) self.actionRun.setText(_translate("ScriptEditor", "&Run")) self.actionRun.setShortcut(_translate("ScriptEditor", "Ctrl+R")) self.actionRunConsole.setText(_translate("ScriptEditor", "Run script in &console")) self.actionRunConsole.setShortcut(_translate("ScriptEditor", "Ctrl+C")) self.actionNewPython.setText(_translate("ScriptEditor", "Python")) self.actionNewPython.setShortcut(_translate("ScriptEditor", "Ctrl+N")) self.actionNewQtQml.setText(_translate("ScriptEditor", "QtQml")) self.actionClear.setText(_translate("ScriptEditor", "Clear")) self.actionClear.setToolTip(_translate("ScriptEditor", "Clear The Console")) self.actionSave_As.setText(_translate("ScriptEditor", "Save &As")) self.actionSave_As.setToolTip(_translate("ScriptEditor", "Save the script")) self.actionSave_As.setShortcut(_translate("ScriptEditor", "Ctrl+A")) self.actionOpen.setText(_translate("ScriptEditor", "&Open")) self.actionOpen.setToolTip(_translate("ScriptEditor", "Open a script")) self.actionOpen.setShortcut(_translate("ScriptEditor", "Ctrl+O")) self.actionSave.setText(_translate("ScriptEditor", "&Save")) self.actionSave.setToolTip(_translate("ScriptEditor", "Save the current script")) self.actionSave.setShortcut(_translate("ScriptEditor", "Ctrl+S")) - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py index 3b3878e460..c331fe6941 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py @@ -1,463 +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: + 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.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) + QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) - def move_start_of_row(self): cursor = self.textCursor() cursor.movePosition( - QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) + QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) - def move_end_of_row(self): cursor = self.textCursor() cursor.movePosition( - QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) + 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 + # 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): + (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:"]" - } + Qt.Key_BraceLeft: " }", + Qt.Key_BracketLeft: "]" + } cursor = self.textCursor() cursor.insertText(close_char[key]) - cursor.setPosition(cursor.position()-1) + 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) + # 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.height(), view.verticalScrollBar().value() + view.viewport().height()) fm = self.fm - w = self.width() - 8 + 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): + 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) + 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" + if __file__ == "": + __file__ = "./widget.py" import sys app = QApplication(sys.argv) src = open(__file__).read() edit = EditorWidget(text=src) - edit.resize(640,480) + 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/assignprofiledialog.py b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py index ae06885a17..3ed01792ab 100644 --- a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py +++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py @@ -1,44 +1,45 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * - + + class AssignProfileDialog(Extension): def __init__(self, parent): super().__init__(parent) def assignProfile(self): doc = Application.activeDocument() if doc == None: QMessageBox.information(Application.activeWindow().qwindow(), "Assign Profile", "There is no active document.") return - + self.dialog = QDialog(Application.activeWindow().qwindow()) - + self.cmbProfile = QComboBox(self.dialog) for profile in sorted(Application.profiles(doc.colorModel(), doc.colorDepth())): self.cmbProfile.addItem(profile) - + vbox = QVBoxLayout(self.dialog) vbox.addWidget(self.cmbProfile) self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.dialog.reject) vbox.addWidget(self.buttonBox) self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() - + def accept(self): doc = Application.activeDocument() doc.setColorProfile(self.cmbProfile.currentText()) def setup(self): action = Application.createAction("assing_profile_to_image", "Assign Profile to Image") action.triggered.connect(self.assignProfile) Scripter.addExtension(AssignProfileDialog(Application)) 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..9193d179dc --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/kritapykrita_colorspace.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=colorspace +X-Python-2-Compatible=false +Name=Color Space +Name[en_GB]=Color Space +Name[pt]=Espaço de Cor +Comment=Plugin to change color space to selected documents +Comment[en_GB]=Plugin to change color space to selected documents +Comment[pt]='Plugin' para alterar o espaço de cor de documentos selecionados 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..e4de2a73cf --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/resources_rc.py @@ -0,0 +1,74 @@ +# -*- 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..beded0f2f6 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/uicolorspace.py @@ -0,0 +1,119 @@ +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..1dbb9ee294 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/kritapykrita_documenttools.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=documenttools +X-Python-2-Compatible=false +Name=Document Tools +Name[en_GB]=Document Tools +Name[pt]=Ferramentas de Documento +Comment=Plugin to manipulate properties of selected documents +Comment[en_GB]=Plugin to manipulate properties of selected documents +Comment[pt]='Plugin' para manipular propriedades de documentos selecionados 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..0be2dbc100 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/canvassizetool/canvassizetool.py @@ -0,0 +1,40 @@ +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..faba9cf065 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/rotatetool/rotatetool.py @@ -0,0 +1,27 @@ +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..7208a62297 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/tools/scaletool/scaletool.py @@ -0,0 +1,48 @@ +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..0ffddf0401 --- /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..027c439ec9 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/exportlayers/kritapykrita_exportlayers.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=exportlayers +X-Python-2-Compatible=false +Name=Export Layers +Name[en_GB]=Export Layers +Name[pt]=Exportar camadas +Comment=Plugin to export layers from a document +Comment[en_GB]=Plugin to export layers from a document +Comment[pt]='Plugin' para exportar camadas de um documento 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..801ca42782 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/exportlayers/uiexportlayers.py @@ -0,0 +1,172 @@ +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..99dd9476dd --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/components/filtercombobox.py @@ -0,0 +1,11 @@ +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..8a9fbd2bc2 --- /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..72ca55828a --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/kritapykrita_filtermanager.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=filtermanager +X-Python-2-Compatible=false +Name=Filter Manager +Name[en_GB]=Filter Manager +Name[pt]=Gerenciador de filtro +Comment=Plugin to filters management +Comment[en_GB]=Plugin to filters management +Comment[pt]='Plugin' para gerenciamento de filtros 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 c4bc7036f6..d9eb4835d2 100644 --- a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py +++ b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py @@ -1,31 +1,35 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + def hello(): QMessageBox.information(QWidget(), "Test", "Hello! This is Krita " + Application.version()) + class HelloExtension(Extension): - def __init__(self, parent): - super().__init__(parent) + def __init__(self, parent): + super().__init__(parent) - def setup(self): - qDebug("Hello Setup") - action = Krita.instance().createAction("hello_python", "hello") - action.triggered.connect(hello) + def setup(self): + qDebug("Hello Setup") + action = Krita.instance().createAction("hello_python", "hello") + action.triggered.connect(hello) Scripter.addExtension(HelloExtension(Krita.instance())) + class HelloDocker(DockWidget): - def __init__(self): - super().__init__() - label = QLabel("Hello", self) - self.setWidget(label) - self.label = label - - def canvasChanged(self, canvas): - self.label.setText("Hellodocker: canvas changed"); + + def __init__(self): + super().__init__() + label = QLabel("Hello", self) + self.setWidget(label) + self.label = label + + def canvasChanged(self, canvas): + self.label.setText("Hellodocker: canvas changed") 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..c27d496812 100644 --- a/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop +++ b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop @@ -1,34 +1,35 @@ [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[sv]=Hello World Name[tr]=Merhaba Dünya Name[uk]=Привіт, світе Name[x-test]=xxHello Worldxx Comment=Basic plugin to test PyKrita Comment[ca]=Connector bàsic per provar el PyKrita Comment[ca@valencia]=Connector bàsic per provar el PyKrita Comment[cs]=Základní modul pro testování PyKrita Comment[en_GB]=Basic plugin to test PyKrita Comment[es]=Complemento básico para probar PyKrita Comment[it]=Estensione di base per provare PyKrita Comment[nl]=Basisplug-in om PyKrita te testen Comment[pl]=Podstawowa wtyczka do wypróbowania PyKrity Comment[pt]='Plugin' básico para testar o PyKrita Comment[sv]=Enkelt insticksprogram för att utprova PyKrita Comment[tr]=PyKrita'yı test etmek için temel eklenti Comment[uk]=Базовий додаток для тестування PyKrita Comment[x-test]=xxBasic plugin to test PyKritaxx diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py b/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py index 61abc85096..2cd1c4a683 100644 --- a/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py +++ b/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py @@ -1,117 +1,116 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + class HighpassExtension(Extension): def __init__(self, parent): super().__init__(parent) def setup(self): action = Application.createAction("high_pass_filter", "High Pass") action.triggered.connect(self.showDialog) def showDialog(self): doc = Application.activeDocument() if doc == None: QMessageBox.information(Application.activeWindow().qwindow(), "Highpass Filter", "There is no active image.") return - + self.dialog = QDialog(Application.activeWindow().qwindow()) self.intRadius = QSpinBox() self.intRadius.setValue(10) self.intRadius.setRange(2, 200) - + self.cmbMode = QComboBox() self.cmbMode.addItems(["Color", "Preserve DC", "Greyscale", "Greyscale, Apply Chroma", "Redrobes"]) - self.keepOriginal = QCheckBox("Keep Original Layer"); + self.keepOriginal = QCheckBox("Keep Original Layer") self.keepOriginal.setChecked(True) form = QFormLayout() form.addRow("Filter Radius", self.intRadius) form.addRow("Mode", self.cmbMode) form.addRow("", self.keepOriginal) - + self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.highpass) self.buttonBox.rejected.connect(self.dialog.reject) - + vbox = QVBoxLayout(self.dialog) vbox.addLayout(form) vbox.addWidget(self.buttonBox) - + self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() - + def highpass(self): # XXX: Start undo macro image = Application.activeDocument() original = image.activeNode() working_layer = original - + # We can only highpass on paint layers if self.keepOriginal.isChecked() or original.type() != "paintlayer": working_layer = image.createNode("working", "paintlayer") working_layer.setColorSpace(original.colorModel(), original.colorSpace(), original.profile()) working_layer.writeBytes(original.readBytes(0, 0, image.width(), image.height()), 0, 0, image.width(), image.height()) - original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented - + original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented + image.setActiveNode(working_layer) - colors_layer = None; - + colors_layer = None + # if keeping colors if self.cmbMode.currentIndex() == 1 or self.cmbMode.currentIndex() == 3: - colors_layer = working_layer.duplicate() # XXX: Unimplemented + colors_layer = working_layer.duplicate() # XXX: Unimplemented colors_layer.setName("colors") - original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented - + original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented + # if greyscale, desature if (self.cmbMode.currentIndex() == 2 or self.cmbMode.currentIndex() == 3): filter = Application.filter("desaturate") filter.apply(working_layer, 0, 0, image.width(), image.height()) - + # Duplicate on top and blur blur_layer = working_layer.duplicate() blur_layer.setName("blur") - original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented - + original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented + # blur filter = Application.filter("gaussian blur") filter_configuration = filter.configuration() filter_configuration.setProperty("horizRadius", self.intRadius.value()) filter_configuration.setProperty("vertRadius", self.intRadius.value()) filter_configuration.setProperty("lockAspect", true) filter.setConfiguration(filter_configuration) filter.apply(blur_layer, 0, 0, image.width(), image.height()) - - + if self.cmbMode.currentIndex() <= 3: blur_layer.setBlendingMode("grain_extract") working_layer = image.mergeDown(blur_layer) - + # if preserve chroma, change set the mode to value and merge down with the layer we kept earlier. if self.cmbMode.currentIndex() == 3: working_layer.setBlendingMode("value") working_layer = image.mergeDown(working_layer) - + # if preserve DC, change set the mode to overlay and merge down with the average colour of the layer we kept earlier. if self.cmbMode.currentIndex() == 1: # get the average color of the entire image # clear the colors layer to the given color working_layer = image.mergeDown(working_layer) - - else: # Mode == 4, RedRobes + + else: # Mode == 4, RedRobes image.setActiveNode(blur_layer) # Get the average color of the input layer # copy the solid colour layer # copy the blurred layer # XXX: End undo macro Scripter.addExtension(HighpassExtension(Krita.instance())) - 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..df92721c9d --- /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..cb4a84bfee --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=lastdocumentsdocker +X-Python-2-Compatible=false +Name=Last Documents Docker +Comment=A Python-based docker for show thumbnails to last ten documents 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..a03d8426c8 --- /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/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 7eb645642a..8d3b59e814 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py @@ -1,143 +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 * +from krita import * + +# import the exporters +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() + widget = QWidget() layout = QVBoxLayout() buttonLayout = QHBoxLayout() widget.setLayout(layout) self.setWindowTitle("Python Palette Docker") - #Make a combobox and add palettes + # 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["Default"]) + 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 + 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() - #self.fill_palette_frame() @pyqtSlot('KoColorSetEntry') def slot_swatchSelected(self, entry): - print("entry "+entry.name) + 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 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 + 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 :) +# 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_exporter_gimppalette.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py index f63e7e12ad..3e8c1af165 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py @@ -1,48 +1,61 @@ ''' -A script that converts the palette named "Default" to a Gimp palette. -This ideally needs some gui and the like to select the palette to export.. +A script that converts the palette with the given name to a gimp palette at the location asked for. By Wolthera. ''' # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * import math from krita import * -allPalettes = Application.resources("palette") -paletteName = "Default" -self.currentPalette = Palette(allPalettes["paletteName"]) -# open the appropriate file... -gplFile = open(paletteName+".gpl", "w") -gplFile.write("GIMP Palette\n") -gplFile.write("Name: "+paletteName+"\n") -gplFile.write("Columns: "+str(self.currentPalette.columnCount())+"\n") -gplFile.write("#"+self.currentPalette.comment()+"\n") -colorCount = self.currentPalette.colorsCountGroup("") - -for i in range(colorCount): - entry = self.currentPalette.colorSetEntryFromGroup(i, "") - color = self.currentPalette.colorForEntry(entry) - #convert to sRGB - color.setColorSpace("RGBA", "U8", "sRGB built-in") - - red = max(min(int(color.componentsOrdered()[0]*255), 255), 0) - green = max(min(int(color.componentsOrdered()[1]*255), 255), 0) - blue = max(min(int(color.componentsOrdered()[2]*255), 255), 0) - gplFile.write(str(red)+" "+str(green)+" "+str(blue)+" "+entry.id+"-"+entry.name+"\n") - groupNames = self.currentPalette.groupNames() - for groupName in groupNames: - colorCount = self.currentPalette.colorsCountGroup(groupName) + +class gimpPaletteExporter: + + def __init__(self, name): + # We want people to select a palette and a location to save to... + self.fileName = QFileDialog.getExistingDirectory() + allPalettes = Application.resources("palette") + self.paletteName = name + self.currentPalette = Palette(allPalettes[self.paletteName]) + self.export() + done = QMessageBox() + done.setWindowTitle("Export succesful") + done.setText(self.paletteName + " has been exported to " + self.fileName + "!") + done.exec_() + pass + + def export(self): + # open the appropriate file... + gplFile = open(self.fileName + "/" + self.paletteName + ".gpl", "w") + gplFile.write("GIMP Palette\n") + gplFile.write("Name: " + self.paletteName + "\n") + gplFile.write("Columns: " + str(self.currentPalette.columnCount()) + "\n") + gplFile.write("#" + self.currentPalette.comment() + "\n") + colorCount = self.currentPalette.colorsCountGroup("") + for i in range(colorCount): - entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) + entry = self.currentPalette.colorSetEntryFromGroup(i, "") color = self.currentPalette.colorForEntry(entry) - #convert to sRGB + # convert to sRGB color.setColorSpace("RGBA", "U8", "sRGB built-in") - red = max(min(int(color.componentsOrdered()[0]*255), 255), 0) - green = max(min(int(color.componentsOrdered()[1]*255), 255), 0) - blue = max(min(int(color.componentsOrdered()[2]*255), 255), 0) - gplFile.write(str(red)+" "+str(green)+" "+str(blue)+" "+entry.id+"-"+entry.name+"\n") -gplFile.close() + + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + gplFile.write(str(red) + " " + str(green) + " " + str(blue) + " " + entry.id + "-" + entry.name + "\n") + groupNames = self.currentPalette.groupNames() + for groupName in groupNames: + colorCount = self.currentPalette.colorsCountGroup(groupName) + for i in range(colorCount): + entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) + color = self.currentPalette.colorForEntry(entry) + # convert to sRGB + color.setColorSpace("RGBA", "U8", "sRGB built-in") + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + gplFile.write(str(red) + " " + str(green) + " " + str(blue) + " " + entry.id + "-" + entry.name + "\n") + gplFile.close() diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py index 8d07f74c97..8bae101a60 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py @@ -1,124 +1,175 @@ ''' A script that converts the palette named "Default" to a SVG so that Inkscape may use the colors -This ideally needs some gui and the like to select the palette to export.. also, the icc-color stuff doesn't work right -ecause we'd need the ability to get the url of the colorprofile somehow, and then we can make @colorprofile things in the definitions. +The icc-color stuff doesn't work right, because we'd need the ability to get the url of the colorprofile somehow, and then we can make color-profile things in the definitions. By Wolthera. ''' # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtXml import * from PyQt5.QtWidgets import * import math from krita import * -allPalettes = Application.resources("palette") -paletteName = "Default" -self.currentPalette = Palette(allPalettes[paletteName]) -# open the appropriate file... -svgFile = open(paletteName+".svg", "w") -svgDoc = QDomDocument() -svgBaseElement = svgDoc.createElement("svg") -svgBaseElement.setAttribute("xmlns:osb", "http://www.openswatchbook.org/uri/2009/osb") -svgBaseElement.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg") -svgDefs = svgDoc.createElement("defs") -svgSwatches = svgDoc.createElement("g") -svgSwatches.setAttribute("id", "Swatches") -Row = 0 -Column = 0 -colorCount = self.currentPalette.colorsCountGroup("") - -for i in range(colorCount): - entry = self.currentPalette.colorSetEntryFromGroup(i, "") - color = self.currentPalette.colorForEntry(entry) +class inkscapeSVGExporter: - iccColor = "icc-color("+color.colorProfile() - for c in range(len(color.componentsOrdered())-1): - iccColor = iccColor+","+str(color.componentsOrdered()[c]) - iccColor = iccColor+")" - #convert to sRGB - color.setColorSpace("RGBA", "U8", "sRGB built-in") - red = max(min(int(color.componentsOrdered()[0]*255), 255), 0) - green = max(min(int(color.componentsOrdered()[1]*255), 255), 0) - blue = max(min(int(color.componentsOrdered()[2]*255), 255), 0) - hexcode = "#"+str(format(red, '02x'))+str(format(green, '02x'))+str(format(blue, '02x')) - swatchName = str(i)+"-"+entry.name - swatchName = swatchName.replace(" ", "-") - swatchMain = svgDoc.createElement("linearGradient") - swatchMain.setAttribute("osb:paint", "solid") - swatchMain.setAttribute("id", swatchName) - swatchSub = svgDoc.createElement("stop") - swatchSub.setAttribute("style", "stop-color: "+hexcode+" "+iccColor+";stop-opacity:1;") - swatchMain.appendChild(swatchSub) - svgDefs.appendChild(swatchMain) - svgSingleSwatch = svgDoc.createElement("rect") - svgSingleSwatch.setAttribute("x", str(int(Column*20))) - svgSingleSwatch.setAttribute("y", str(int(Row*20))) - svgSingleSwatch.setAttribute("width", str(int(20))) - svgSingleSwatch.setAttribute("height", str(int(20))) - svgSingleSwatch.setAttribute("fill", "url(#"+swatchName+")") - svgSingleSwatch.setAttribute("id", "swatch"+swatchName) - svgSwatches.appendChild(svgSingleSwatch) - Column += 1 - if (Column >= self.currentPalette.columnCount()): + def __init__(self, name): + # We want people to select a palette and a location to save to... + self.fileName = QFileDialog.getExistingDirectory() + allPalettes = Application.resources("palette") + self.paletteName = name + self.currentPalette = Palette(allPalettes[self.paletteName]) + self.export() + done = QMessageBox() + done.setWindowTitle("Export succesful") + done.setText(self.paletteName + " has been exported to " + self.fileName + "!") + done.exec_() + pass + + def export(self): + # open the appropriate file... + svgFile = open(self.fileName + "/" + self.paletteName + ".svg", "w") + svgDoc = QDomDocument() + svgBaseElement = svgDoc.createElement("svg") + svgBaseElement.setAttribute("xmlns:osb", "http://www.openswatchbook.org/uri/2009/osb") + svgBaseElement.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg") + svgBaseElement.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/") + svgBaseElement.setAttribute("xmlns:cc", "http://creativecommons.org/ns#") + svgBaseElement.setAttribute("xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") + svgDefs = svgDoc.createElement("defs") + svgSwatches = svgDoc.createElement("g") + svgSwatches.setAttribute("id", "Swatches") + + svgMeta = svgDoc.createElement("metadata") + svgBaseElement.appendChild(svgMeta) + rdf = svgDoc.createElement("rdf:RDF") + ccwork = svgDoc.createElement("cc:Work") + dctitle = svgDoc.createElement("dc:title") + dcdescription = svgDoc.createElement("dc:description") + dctitle.appendChild(svgDoc.createTextNode(self.paletteName)) + dcdescription.appendChild(svgDoc.createTextNode(self.currentPalette.comment())) + ccwork.appendChild(dctitle) + ccwork.appendChild(dcdescription) + rdf.appendChild(ccwork) + svgMeta.appendChild(rdf) + Row = 0 Column = 0 - Row +=1 - -groupNames = self.currentPalette.groupNames() -for groupName in groupNames: - Column=0 - Row+=1 - groupTitle = svgDoc.createElement("text") - groupTitle.setAttribute("x", str(int(Column*20))) - groupTitle.setAttribute("y", str(int(Row*20)+15)) - groupTitle.appendChild(svgDoc.createTextNode(groupName)) - svgSwatches.appendChild(groupTitle) - Row+=1 - colorCount = self.currentPalette.colorsCountGroup(groupName) - for i in range(colorCount): - entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) - color = self.currentPalette.colorForEntry(entry) - iccColor = "icc-color("+color.colorProfile() - for c in range(len(color.componentsOrdered())-1): - iccColor = iccColor+","+str(color.componentsOrdered()[c]) - iccColor = iccColor+")" - #convert to sRGB - color.setColorSpace("RGBA", "U8", "sRGB built-in") - red = max(min(int(color.componentsOrdered()[0]*255), 255), 0) - green = max(min(int(color.componentsOrdered()[1]*255), 255), 0) - blue = max(min(int(color.componentsOrdered()[2]*255), 255), 0) - hexcode = "#"+str(format(red, '02x'))+str(format(green, '02x'))+str(format(blue, '02x')) - - swatchName = groupName+str(i)+"-"+entry.name - swatchName = swatchName.replace(" ", "-") - swatchMain = svgDoc.createElement("linearGradient") - swatchMain.setAttribute("osb:paint", "solid") - swatchMain.setAttribute("id", swatchName) - swatchSub = svgDoc.createElement("stop") - swatchSub.setAttribute("style", "stop-color: "+hexcode+" "+iccColor+";stop-opacity:1;") - swatchMain.appendChild(swatchSub) - svgDefs.appendChild(swatchMain) - svgSingleSwatch = svgDoc.createElement("rect") - svgSingleSwatch.setAttribute("x", str(int(Column*20))) - svgSingleSwatch.setAttribute("y", str(int(Row*20))) - svgSingleSwatch.setAttribute("width", str(int(20))) - svgSingleSwatch.setAttribute("height", str(int(20))) - svgSingleSwatch.setAttribute("fill", "url(#"+swatchName+")") - svgSingleSwatch.setAttribute("id", "swatch"+swatchName) - svgSwatches.appendChild(svgSingleSwatch) - Column += 1 - if (Column >= self.currentPalette.columnCount()): - Column = 0 - Row +=1 + iccProfileList = [] + + colorCount = self.currentPalette.colorsCountGroup("") -svgBaseElement.appendChild(svgDefs) -svgBaseElement.appendChild(svgSwatches) -svgBaseElement.setAttribute("viewBox", "0 0 "+str(self.currentPalette.columnCount()*20)+" "+str(int(Row*20))) -svgDoc.appendChild(svgBaseElement) -svgFile.write(svgDoc.toString()) -svgFile.close() + for i in range(colorCount): + entry = self.currentPalette.colorSetEntryFromGroup(i, "") + color = self.currentPalette.colorForEntry(entry) + + iccColor = "icc-color(" + color.colorProfile() + for c in range(len(color.componentsOrdered()) - 1): + iccColor = iccColor + "," + str(color.componentsOrdered()[c]) + iccColor = iccColor + ")" + if color.colorProfile() not in iccProfileList: + iccProfileList.append(color.colorProfile()) + + # convert to sRGB + color.setColorSpace("RGBA", "U8", "sRGB built-in") + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + hexcode = "#" + str(format(red, '02x')) + str(format(green, '02x')) + str(format(blue, '02x')) + swatchName = str(i) + "-" + entry.name + swatchName = swatchName.replace(" ", "-") + swatchName = swatchName.replace("(", "-") + swatchName = swatchName.replace(")", "-") + swatchMain = svgDoc.createElement("linearGradient") + swatchMain.setAttribute("osb:paint", "solid") + swatchMain.setAttribute("id", swatchName) + swatchSub = svgDoc.createElement("stop") + swatchSub.setAttribute("style", "stop-color: " + hexcode + " " + iccColor + ";stop-opacity:1;") + swatchMain.appendChild(swatchSub) + svgDefs.appendChild(swatchMain) + svgSingleSwatch = svgDoc.createElement("rect") + svgSingleSwatch.setAttribute("x", str(int(Column * 20))) + svgSingleSwatch.setAttribute("y", str(int(Row * 20))) + svgSingleSwatch.setAttribute("width", str(int(20))) + svgSingleSwatch.setAttribute("height", str(int(20))) + svgSingleSwatch.setAttribute("fill", "url(#" + swatchName + ")") + svgSingleSwatch.setAttribute("id", "swatch" + swatchName) + if entry.spotColor is True: + svgSingleSwatch.setAttribute("rx", str(10)) + svgSingleSwatch.setAttribute("ry", str(10)) + svgSwatches.appendChild(svgSingleSwatch) + Column += 1 + if (Column >= self.currentPalette.columnCount()): + Column = 0 + Row += 1 + + groupNames = self.currentPalette.groupNames() + for groupName in groupNames: + Column = 0 + Row += 1 + groupTitle = svgDoc.createElement("text") + groupTitle.setAttribute("x", str(int(Column * 20))) + groupTitle.setAttribute("y", str(int(Row * 20) + 15)) + groupTitle.appendChild(svgDoc.createTextNode(groupName)) + svgSwatches.appendChild(groupTitle) + Row += 1 + colorCount = self.currentPalette.colorsCountGroup(groupName) + for i in range(colorCount): + entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) + color = self.currentPalette.colorForEntry(entry) + iccColor = "icc-color(" + color.colorProfile() + for c in range(len(color.componentsOrdered()) - 1): + iccColor = iccColor + "," + str(color.componentsOrdered()[c]) + iccColor = iccColor + ")" + if color.colorProfile() not in iccProfileList: + iccProfileList.append(color.colorProfile()) + # convert to sRGB + color.setColorSpace("RGBA", "U8", "sRGB built-in") + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + hexcode = "#" + str(format(red, '02x')) + str(format(green, '02x')) + str(format(blue, '02x')) + swatchName = groupName + str(i) + "-" + entry.name + swatchName = swatchName.replace(" ", "-") + swatchName = swatchName.replace("(", "-") + swatchName = swatchName.replace(")", "-") + swatchMain = svgDoc.createElement("linearGradient") + swatchMain.setAttribute("osb:paint", "solid") + swatchMain.setAttribute("id", swatchName) + swatchSub = svgDoc.createElement("stop") + swatchSub.setAttribute("style", "stop-color: " + hexcode + " " + iccColor + ";stop-opacity:1;") + swatchMain.appendChild(swatchSub) + svgDefs.appendChild(swatchMain) + svgSingleSwatch = svgDoc.createElement("rect") + svgSingleSwatch.setAttribute("x", str(int(Column * 20))) + svgSingleSwatch.setAttribute("y", str(int(Row * 20))) + svgSingleSwatch.setAttribute("width", str(int(20))) + svgSingleSwatch.setAttribute("height", str(int(20))) + svgSingleSwatch.setAttribute("fill", "url(#" + swatchName + ")") + svgSingleSwatch.setAttribute("id", "swatch " + swatchName) + if entry.spotColor is True: + svgSingleSwatch.setAttribute("rx", str(10)) + svgSingleSwatch.setAttribute("ry", str(10)) + svgSwatches.appendChild(svgSingleSwatch) + Column += 1 + if (Column >= self.currentPalette.columnCount()): + Column = 0 + Row += 1 + for profile in iccProfileList: + svgProfileDesc = svgDoc.createElement("color-profile") + svgProfileDesc.setAttribute("name", profile) + # This is incomplete because python api doesn't have any way to ask for this data yet. + # svgProfileDesc.setAttribute("local", "sRGB") + # svgProfileDesc.setAttribute("xlink:href", colorprofileurl) + svgProfileDesc.setAttribute("rendering-intent", "perceptual") + svgDefs.appendChild(svgProfileDesc) + svgBaseElement.appendChild(svgDefs) + svgBaseElement.appendChild(svgSwatches) + svgBaseElement.setAttribute("viewBox", "0 0 " + str(self.currentPalette.columnCount() * 20) + " " + str(int((Row + 1) * 20))) + svgDoc.appendChild(svgBaseElement) + svgFile.write(svgDoc.toString()) + svgFile.close() 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..aa3971f582 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_sortColors.py @@ -0,0 +1,68 @@ +''' +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/kritapykrita_quick_settings_docker.desktop b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop index 61f8eff94b..a0fb1bf2bb 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,7 +1,23 @@ [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[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[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 86015be5e8..6159c7c23b 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,160 +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 * +from krita import * + class QuickSettingsDocker(DockWidget): -#Init the docker +# Init the docker + def __init__(self): super().__init__() # make base-widget and layout - widget = QWidget() + 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.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. + + # 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.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. + # 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 + 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.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.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.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) + 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); + Application.activeWindow().views()[0].setBrushSize(brushSize) + @pyqtSlot('QModelIndex') def setBrushOpacity(self, index): - i = index.column()+(index.row()*self.columns) - brushOpacity = self.opacityList[i]/100 + 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); + Application.activeWindow().views()[0].setPaintingOpacity(brushOpacity) + @pyqtSlot('QModelIndex') def setBrushFlow(self, index): - i = index.column()+(index.row()*self.columns) - brushOpacity = self.opacityList[i]/100 + 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); - + Application.activeWindow().views()[0].setPaintingFlow(brushOpacity) -#Add docker to the application :) -Application.addDockWidgetFactory(DockWidgetFactory("quick_settings_docker", DockWidgetFactoryBase.DockRight, QuickSettingsDocker)) +# 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..3447a4dc75 --- /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..bb93c3eb77 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/kritapykrita_scriptdocker.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=scriptdocker +X-Python-2-Compatible=false +Name=Script Docker +Comment=A Python-based docker for create actions and point to Python scripts 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..a7ee822aa3 --- /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/debugger_scripter/debugger.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py index 017ca0a0f2..68ea0b0470 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py @@ -1,99 +1,99 @@ import bdb import multiprocessing import asyncio from . import debuggerformatter class Debugger(bdb.Bdb): def __init__(self, scripter, cmd): bdb.Bdb.__init__(self) self.quit = False self.debugq = multiprocessing.Queue() self.scripter = scripter self.applicationq = multiprocessing.Queue() self.filePath = self.scripter.documentcontroller.activeDocument.filePath self.application_data = {} self.exception_data = {} self.debugprocess = multiprocessing.Process(target=self._run, args=(self.filePath,)) self.currentLine = 0 bdb.Bdb.reset(self) def _run(self, filename): try: self.mainpyfile = self.canonic(filename) with open(filename, "rb") as fp: statement = "exec(compile(%r, %r, 'exec'))" % \ (fp.read(), self.mainpyfile) self.run(statement) except Exception as e: raise e def user_call(self, frame, args): name = frame.f_code.co_name or "" def user_line(self, frame): """Handler that executes with every line of code""" co = frame.f_code - if self.filePath!=co.co_filename: + if self.filePath != co.co_filename: return self.currentLine = frame.f_lineno - self.applicationq.put({ "code": { "file": co.co_filename, - "name": co.co_name, - "lineNumber": str(frame.f_lineno) + self.applicationq.put({"code": {"file": co.co_filename, + "name": co.co_name, + "lineNumber": str(frame.f_lineno) }, - "frame": { "firstLineNumber": co.co_firstlineno, - "locals": debuggerformatter.format_data(frame.f_locals), - "globals": debuggerformatter.format_data(frame.f_globals) - }, - "trace": "line" - }) + "frame": {"firstLineNumber": co.co_firstlineno, + "locals": debuggerformatter.format_data(frame.f_locals), + "globals": debuggerformatter.format_data(frame.f_globals) + }, + "trace": "line" + }) if self.quit: return self.set_quit() - if self.currentLine==0: + if self.currentLine == 0: return else: cmd = self.debugq.get() if cmd == "step": return if cmd == "stop": return self.set_quit() def user_return(self, frame, value): name = frame.f_code.co_name or "" if name == '': - self.applicationq.put({ "quit": True}) + self.applicationq.put({"quit": True}) def user_exception(self, frame, exception): - self.applicationq.put({ "exception": str(exception[1])}) + self.applicationq.put({"exception": str(exception[1])}) async def display(self): """Coroutine for updating the UI""" while True: if self.applicationq.empty(): await asyncio.sleep(0.3) else: while not self.applicationq.empty(): self.application_data.update(self.applicationq.get()) self.scripter.uicontroller.repaintDebugArea() return async def start(self): await self.display() async def step(self): self.debugq.put("step") await self.display() async def stop(self): self.debugq.put("stop") - self.applicationq.put({ "quit": True}) + self.applicationq.put({"quit": True}) await self.display() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py index ff3f6a3c93..64c8018b62 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py @@ -1,26 +1,26 @@ import re import inspect def format_data(data): globals()['types'] = __import__('types') exclude_keys = ['copyright', 'credits', 'False', 'True', 'None', 'Ellipsis', 'quit', 'QtCriticalMsg', 'krita_path', 'QtWarningMsg', 'QWIDGETSIZE_MAX', 'QtFatalMsg', 'PYQT_CONFIGURATION', 'on_load', 'PYQT_VERSION', 'on_pykrita_unloading', 'on_unload', 'QT_VERSION', 'QtInfoMsg', 'PYQT_VERSION_STR', 'qApp', 'QtSystemMsg', 'QtDebugMsg', 'on_pykrita_loaded', 'QT_VERSION_STR'] exclude_valuetypes = [types.BuiltinFunctionType, types.BuiltinMethodType, types.ModuleType, types.FunctionType] return [{k: {'value': str(v), 'type': str(type(v))}} for k, v in data.items() if not (k in exclude_keys or - type(v) in exclude_valuetypes or - re.search(r'^(__).*\1$', k) or - inspect.isclass(v) or - inspect.isfunction(v))] + type(v) in exclude_valuetypes or + re.search(r'^(__).*\1$', k) or + inspect.isclass(v) or + inspect.isfunction(v))] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py b/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py index 5436380a5b..09734cdc5d 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py @@ -1,542 +1,544 @@ # -*- coding: utf-8 -*- # Resource object code # # Created by: The Resource Compiler for PyQt5 (Qt v5.8.0) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = b"\ \x00\x00\x0f\xd9\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ \x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ \x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ \x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\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\x22\x3e\x0a\x3c\x73\x76\ \x67\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\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ \x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ \x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\ \x36\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ \x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ \x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\ \x36\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ \x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\x3e\x0a\x09\x3c\ \x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x37\x36\x2e\x34\x39\x33\ \x2c\x33\x31\x30\x2e\x39\x39\x38\x63\x2d\x31\x2e\x35\x37\x37\x2d\ \x32\x39\x2e\x31\x36\x37\x2d\x32\x34\x2e\x39\x37\x32\x2d\x35\x32\ \x2e\x35\x36\x32\x2d\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\ \x33\x39\x76\x2d\x32\x31\x2e\x37\x32\x68\x2d\x36\x2e\x32\x33\x35\ \x76\x32\x31\x2e\x37\x32\x20\x20\x20\x63\x2d\x32\x39\x2e\x31\x36\ \x37\x2c\x31\x2e\x35\x37\x37\x2d\x35\x32\x2e\x35\x36\x32\x2c\x32\ \x34\x2e\x39\x37\x32\x2d\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\ \x31\x33\x39\x68\x2d\x32\x31\x2e\x34\x36\x35\x76\x36\x2e\x32\x33\ \x35\x68\x32\x31\x2e\x34\x36\x35\x63\x31\x2e\x35\x37\x37\x2c\x32\ \x39\x2e\x31\x36\x37\x2c\x32\x34\x2e\x39\x37\x32\x2c\x35\x32\x2e\ \x35\x36\x38\x2c\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\x31\x34\ \x35\x76\x32\x31\x2e\x37\x31\x35\x68\x36\x2e\x32\x33\x35\x76\x2d\ \x32\x31\x2e\x37\x31\x35\x20\x20\x20\x63\x32\x39\x2e\x31\x36\x37\ \x2d\x31\x2e\x35\x37\x36\x2c\x35\x32\x2e\x35\x36\x32\x2d\x32\x34\ \x2e\x39\x37\x38\x2c\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\ \x34\x35\x68\x32\x36\x2e\x31\x34\x31\x76\x2d\x36\x2e\x32\x33\x35\ \x48\x33\x37\x36\x2e\x34\x39\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\ \x31\x39\x2c\x33\x36\x35\x2e\x31\x34\x33\x63\x2d\x32\x35\x2e\x37\ \x33\x33\x2d\x31\x2e\x35\x36\x2d\x34\x36\x2e\x33\x34\x35\x2d\x32\ \x32\x2e\x31\x37\x38\x2d\x34\x37\x2e\x39\x30\x33\x2d\x34\x37\x2e\ \x39\x31\x20\x20\x20\x68\x34\x37\x2e\x39\x30\x33\x56\x33\x36\x35\ \x2e\x31\x34\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\x31\x39\x2c\x33\ \x31\x30\x2e\x39\x39\x38\x68\x2d\x34\x37\x2e\x39\x30\x33\x63\x31\ \x2e\x35\x35\x39\x2d\x32\x35\x2e\x37\x33\x32\x2c\x32\x32\x2e\x31\ \x37\x2d\x34\x36\x2e\x33\x34\x35\x2c\x34\x37\x2e\x39\x30\x33\x2d\ \x34\x37\x2e\x39\x30\x33\x56\x33\x31\x30\x2e\x39\x39\x38\x7a\x20\ \x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\x33\ \x76\x2d\x34\x37\x2e\x39\x31\x68\x34\x37\x2e\x39\x30\x33\x20\x20\ \x20\x43\x33\x36\x38\x2e\x36\x39\x32\x2c\x33\x34\x32\x2e\x39\x36\ \x35\x2c\x33\x34\x38\x2e\x30\x38\x37\x2c\x33\x36\x33\x2e\x35\x38\ \x33\x2c\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\ \x33\x7a\x20\x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x31\x30\x2e\ \x39\x39\x38\x76\x2d\x34\x37\x2e\x39\x30\x33\x63\x32\x35\x2e\x37\ \x33\x32\x2c\x31\x2e\x35\x35\x39\x2c\x34\x36\x2e\x33\x34\x34\x2c\ \x32\x32\x2e\x31\x37\x31\x2c\x34\x37\x2e\x39\x30\x33\x2c\x34\x37\ \x2e\x39\x30\x33\x48\x33\x32\x32\x2e\x33\x35\x34\x7a\x20\x20\x20\ \x20\x4d\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\x31\x36\ \x63\x2d\x30\x2e\x37\x35\x35\x2d\x34\x2e\x36\x37\x36\x2c\x32\x2e\ \x34\x33\x2d\x39\x2e\x30\x39\x31\x2c\x37\x2e\x31\x30\x36\x2d\x39\ \x2e\x38\x35\x32\x63\x32\x2e\x30\x36\x34\x2d\x30\x2e\x33\x33\x38\ \x2c\x35\x30\x2e\x35\x39\x35\x2d\x38\x2e\x37\x31\x36\x2c\x35\x33\ \x2e\x35\x31\x32\x2d\x35\x30\x2e\x35\x33\x31\x20\x20\x20\x63\x30\ \x2e\x33\x32\x32\x2d\x34\x2e\x36\x37\x37\x2c\x34\x2e\x33\x36\x35\ \x2d\x38\x2e\x32\x35\x37\x2c\x39\x2e\x31\x35\x31\x2d\x37\x2e\x39\ \x37\x34\x63\x34\x2e\x37\x33\x37\x2c\x30\x2e\x33\x33\x35\x2c\x38\ \x2e\x33\x31\x32\x2c\x34\x2e\x34\x34\x35\x2c\x37\x2e\x39\x38\x39\ \x2c\x39\x2e\x31\x37\x63\x2d\x33\x2e\x38\x37\x39\x2c\x35\x35\x2e\ \x35\x31\x32\x2d\x36\x37\x2e\x32\x39\x31\x2c\x36\x36\x2e\x32\x30\ \x31\x2d\x36\x37\x2e\x39\x33\x31\x2c\x36\x36\x2e\x33\x30\x32\x20\ \x20\x20\x63\x2d\x30\x2e\x34\x33\x38\x2c\x30\x2e\x30\x36\x37\x2d\ \x30\x2e\x39\x30\x31\x2c\x30\x2e\x31\x30\x34\x2d\x31\x2e\x33\x35\ \x38\x2c\x30\x2e\x31\x30\x34\x43\x32\x39\x35\x2e\x35\x37\x34\x2c\ \x39\x37\x2e\x32\x33\x35\x2c\x32\x39\x32\x2e\x30\x31\x32\x2c\x39\ \x34\x2e\x32\x2c\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\ \x31\x36\x7a\x20\x4d\x33\x32\x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\ \x33\x20\x20\x20\x63\x2d\x30\x2e\x33\x32\x39\x2d\x34\x2e\x37\x32\ \x35\x2c\x33\x2e\x32\x34\x39\x2d\x38\x2e\x38\x33\x35\x2c\x37\x2e\ \x39\x37\x31\x2d\x39\x2e\x31\x36\x34\x63\x34\x2e\x38\x37\x34\x2d\ \x30\x2e\x33\x34\x34\x2c\x38\x2e\x38\x34\x31\x2c\x33\x2e\x32\x39\ \x31\x2c\x39\x2e\x31\x37\x2c\x37\x2e\x39\x36\x38\x63\x32\x2e\x39\ \x32\x2c\x34\x31\x2e\x38\x32\x2c\x35\x31\x2e\x34\x34\x31\x2c\x35\ \x30\x2e\x31\x39\x39\x2c\x35\x33\x2e\x35\x31\x32\x2c\x35\x30\x2e\ \x35\x33\x37\x20\x20\x20\x63\x34\x2e\x36\x37\x2c\x30\x2e\x37\x36\ \x31\x2c\x37\x2e\x38\x35\x38\x2c\x35\x2e\x31\x37\x36\x2c\x37\x2e\ \x31\x30\x39\x2c\x39\x2e\x38\x34\x36\x63\x2d\x30\x2e\x36\x37\x39\ \x2c\x34\x2e\x31\x38\x34\x2d\x34\x2e\x32\x34\x31\x2c\x37\x2e\x32\ \x32\x35\x2d\x38\x2e\x34\x37\x33\x2c\x37\x2e\x32\x32\x35\x63\x2d\ \x30\x2e\x34\x35\x37\x2c\x30\x2d\x30\x2e\x39\x31\x36\x2d\x30\x2e\ \x30\x33\x37\x2d\x31\x2e\x33\x37\x39\x2d\x30\x2e\x31\x31\x20\x20\ \x20\x43\x39\x39\x2e\x36\x36\x38\x2c\x39\x37\x2e\x30\x33\x31\x2c\ \x33\x36\x2e\x32\x35\x35\x2c\x38\x36\x2e\x33\x34\x31\x2c\x33\x32\ \x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\x33\x7a\x20\x4d\x33\x33\x30\ \x2e\x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x63\x32\x2e\x34\ \x34\x38\x2c\x30\x2e\x31\x30\x31\x2c\x36\x30\x2e\x39\x32\x32\x2c\ \x33\x2e\x30\x36\x39\x2c\x37\x32\x2e\x36\x35\x35\x2c\x35\x30\x2e\ \x30\x31\x37\x20\x20\x20\x63\x30\x2e\x35\x35\x35\x2c\x32\x2e\x32\ \x32\x39\x2c\x30\x2e\x32\x31\x34\x2c\x34\x2e\x35\x33\x36\x2d\x30\ \x2e\x39\x36\x32\x2c\x36\x2e\x35\x30\x33\x63\x2d\x31\x2e\x31\x38\ \x31\x2c\x31\x2e\x39\x36\x37\x2d\x33\x2e\x30\x36\x32\x2c\x33\x2e\ \x33\x36\x31\x2d\x35\x2e\x32\x39\x31\x2c\x33\x2e\x39\x31\x63\x2d\ \x30\x2e\x36\x37\x2c\x30\x2e\x31\x38\x32\x2d\x31\x2e\x33\x38\x32\ \x2c\x30\x2e\x32\x36\x38\x2d\x32\x2e\x30\x38\x33\x2c\x30\x2e\x32\ \x36\x38\x20\x20\x20\x63\x2d\x33\x2e\x39\x34\x35\x2c\x30\x2d\x37\ \x2e\x33\x36\x37\x2d\x32\x2e\x36\x38\x2d\x38\x2e\x33\x33\x2d\x36\ \x2e\x35\x30\x34\x63\x2d\x38\x2e\x35\x39\x32\x2d\x33\x34\x2e\x33\ \x38\x32\x2d\x35\x36\x2e\x32\x37\x2d\x33\x37\x2e\x30\x31\x2d\x35\ \x36\x2e\x37\x35\x2d\x33\x37\x2e\x30\x32\x37\x63\x2d\x32\x2e\x32\ \x38\x34\x2d\x30\x2e\x30\x39\x35\x2d\x34\x2e\x34\x30\x33\x2d\x31\ \x2e\x30\x37\x38\x2d\x35\x2e\x39\x35\x36\x2d\x32\x2e\x37\x36\x38\ \x20\x20\x20\x63\x2d\x31\x2e\x35\x35\x33\x2d\x31\x2e\x36\x39\x2d\ \x32\x2e\x33\x35\x36\x2d\x33\x2e\x38\x38\x35\x2d\x32\x2e\x32\x35\ \x39\x2d\x36\x2e\x31\x38\x31\x63\x30\x2e\x31\x39\x35\x2d\x34\x2e\ \x36\x31\x32\x2c\x33\x2e\x39\x33\x39\x2d\x38\x2e\x32\x32\x39\x2c\ \x38\x2e\x35\x33\x31\x2d\x38\x2e\x32\x32\x39\x4c\x33\x33\x30\x2e\ \x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x7a\x20\x4d\x37\x33\ \x2e\x36\x38\x36\x2c\x31\x37\x39\x2e\x36\x39\x38\x20\x20\x20\x63\ \x2d\x30\x2e\x34\x37\x32\x2c\x30\x2e\x30\x32\x34\x2d\x34\x38\x2e\ \x31\x39\x33\x2c\x32\x2e\x37\x36\x31\x2d\x35\x36\x2e\x37\x36\x2c\ \x33\x37\x2e\x30\x32\x37\x63\x2d\x30\x2e\x39\x35\x39\x2c\x33\x2e\ \x38\x32\x34\x2d\x34\x2e\x33\x38\x31\x2c\x36\x2e\x35\x30\x34\x2d\ \x38\x2e\x33\x33\x2c\x36\x2e\x35\x30\x34\x63\x2d\x30\x2e\x37\x30\ \x33\x2c\x30\x2d\x31\x2e\x34\x31\x2d\x30\x2e\x30\x38\x36\x2d\x32\ \x2e\x30\x38\x38\x2d\x30\x2e\x32\x36\x38\x20\x20\x20\x63\x2d\x32\ \x2e\x32\x32\x2d\x30\x2e\x35\x34\x39\x2d\x34\x2e\x30\x39\x35\x2d\ \x31\x2e\x39\x34\x33\x2d\x35\x2e\x32\x38\x32\x2d\x33\x2e\x39\x31\ \x63\x2d\x31\x2e\x31\x37\x39\x2d\x31\x2e\x39\x36\x37\x2d\x31\x2e\ \x35\x32\x2d\x34\x2e\x32\x37\x34\x2d\x30\x2e\x39\x36\x35\x2d\x36\ \x2e\x35\x30\x33\x63\x31\x31\x2e\x37\x33\x34\x2d\x34\x36\x2e\x39\ \x34\x37\x2c\x37\x30\x2e\x32\x30\x38\x2d\x34\x39\x2e\x39\x31\x36\ \x2c\x37\x32\x2e\x36\x39\x32\x2d\x35\x30\x2e\x30\x32\x32\x6c\x30\ \x2e\x33\x35\x39\x2d\x30\x2e\x30\x30\x36\x20\x20\x20\x63\x34\x2e\ \x36\x31\x35\x2c\x30\x2c\x38\x2e\x33\x38\x31\x2c\x33\x2e\x36\x31\ \x31\x2c\x38\x2e\x35\x38\x2c\x38\x2e\x32\x31\x37\x43\x38\x32\x2e\ \x30\x39\x38\x2c\x31\x37\x35\x2e\x34\x37\x31\x2c\x37\x38\x2e\x34\ \x32\x2c\x31\x37\x39\x2e\x34\x38\x37\x2c\x37\x33\x2e\x36\x38\x36\ \x2c\x31\x37\x39\x2e\x36\x39\x38\x7a\x20\x4d\x31\x36\x30\x2e\x39\ \x32\x35\x2c\x34\x39\x2e\x31\x30\x36\x20\x20\x20\x63\x2d\x30\x2e\ \x32\x31\x39\x2d\x31\x2e\x37\x30\x38\x2d\x30\x2e\x33\x33\x32\x2d\ \x33\x2e\x34\x32\x38\x2d\x30\x2e\x33\x33\x32\x2d\x35\x2e\x31\x33\ \x33\x63\x30\x2d\x32\x32\x2e\x32\x38\x39\x2c\x31\x38\x2e\x31\x34\ \x2d\x34\x30\x2e\x34\x32\x39\x2c\x34\x30\x2e\x34\x32\x39\x2d\x34\ \x30\x2e\x34\x32\x39\x63\x32\x32\x2e\x32\x39\x33\x2c\x30\x2c\x34\ \x30\x2e\x34\x32\x36\x2c\x31\x38\x2e\x31\x34\x2c\x34\x30\x2e\x34\ \x32\x36\x2c\x34\x30\x2e\x34\x32\x39\x20\x20\x20\x63\x30\x2c\x31\ \x2e\x37\x30\x35\x2d\x30\x2e\x31\x31\x35\x2c\x33\x2e\x34\x31\x39\ \x2d\x30\x2e\x33\x33\x35\x2c\x35\x2e\x31\x33\x33\x63\x32\x34\x2e\ \x32\x36\x36\x2c\x31\x30\x2e\x36\x39\x32\x2c\x34\x35\x2e\x32\x33\ \x36\x2c\x33\x31\x2e\x31\x31\x33\x2c\x35\x39\x2e\x32\x39\x31\x2c\ \x35\x37\x2e\x37\x37\x31\x6c\x31\x2e\x36\x30\x37\x2c\x33\x2e\x30\ \x35\x34\x6c\x2d\x33\x2e\x30\x36\x39\x2c\x31\x2e\x35\x38\x39\x20\ \x20\x20\x63\x2d\x32\x36\x2e\x33\x34\x38\x2c\x31\x33\x2e\x37\x30\ \x37\x2d\x35\x35\x2e\x37\x35\x32\x2c\x32\x30\x2e\x36\x35\x38\x2d\ \x38\x37\x2e\x33\x39\x32\x2c\x32\x30\x2e\x36\x35\x38\x6c\x30\x2c\ \x30\x63\x2d\x34\x36\x2e\x38\x34\x31\x2c\x30\x2d\x38\x36\x2e\x38\ \x30\x34\x2d\x31\x35\x2e\x30\x31\x33\x2d\x31\x30\x36\x2e\x34\x35\ \x37\x2d\x32\x33\x2e\x39\x36\x37\x6c\x2d\x33\x2e\x33\x39\x38\x2d\ \x31\x2e\x35\x35\x33\x6c\x31\x2e\x38\x32\x37\x2d\x33\x2e\x32\x36\ \x31\x20\x20\x20\x43\x31\x31\x37\x2e\x34\x39\x34\x2c\x37\x38\x2e\ \x34\x39\x33\x2c\x31\x33\x37\x2e\x38\x30\x34\x2c\x35\x39\x2e\x33\ \x30\x36\x2c\x31\x36\x30\x2e\x39\x32\x35\x2c\x34\x39\x2e\x31\x30\ \x36\x7a\x20\x4d\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\ \x32\x33\x35\x63\x31\x2e\x31\x34\x38\x2c\x34\x2e\x35\x39\x32\x2d\ \x31\x2e\x36\x35\x33\x2c\x39\x2e\x32\x36\x32\x2d\x36\x2e\x32\x34\ \x31\x2c\x31\x30\x2e\x34\x31\x32\x20\x20\x20\x63\x2d\x30\x2e\x34\ \x34\x31\x2c\x30\x2e\x31\x32\x32\x2d\x34\x36\x2e\x30\x39\x38\x2c\ \x31\x32\x2e\x33\x39\x38\x2d\x35\x32\x2e\x30\x30\x38\x2c\x35\x33\ \x2e\x30\x33\x37\x63\x2d\x30\x2e\x36\x31\x32\x2c\x34\x2e\x31\x38\ \x39\x2d\x34\x2e\x32\x36\x35\x2c\x37\x2e\x33\x35\x35\x2d\x38\x2e\ \x34\x39\x34\x2c\x37\x2e\x33\x35\x35\x63\x2d\x30\x2e\x34\x31\x31\ \x2c\x30\x2d\x30\x2e\x38\x32\x38\x2d\x30\x2e\x30\x32\x34\x2d\x31\ \x2e\x32\x34\x35\x2d\x30\x2e\x30\x39\x38\x20\x20\x20\x63\x2d\x34\ \x2e\x36\x38\x33\x2d\x30\x2e\x36\x37\x36\x2d\x37\x2e\x39\x34\x36\ \x2d\x35\x2e\x30\x34\x38\x2d\x37\x2e\x32\x36\x35\x2d\x39\x2e\x37\ \x33\x36\x63\x37\x2e\x36\x31\x38\x2d\x35\x32\x2e\x33\x34\x39\x2c\ \x36\x32\x2e\x35\x30\x32\x2d\x36\x36\x2e\x36\x34\x36\x2c\x36\x34\ \x2e\x38\x33\x34\x2d\x36\x37\x2e\x32\x33\x63\x30\x2e\x36\x37\x39\ \x2d\x30\x2e\x31\x37\x2c\x31\x2e\x33\x38\x38\x2d\x30\x2e\x32\x35\ \x36\x2c\x32\x2e\x30\x39\x35\x2d\x30\x2e\x32\x35\x36\x20\x20\x20\ \x43\x31\x31\x31\x2e\x36\x32\x31\x2c\x32\x39\x31\x2e\x37\x33\x32\ \x2c\x31\x31\x35\x2e\x30\x34\x36\x2c\x32\x39\x34\x2e\x33\x39\x39\ \x2c\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\x32\x33\x35\ \x7a\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\x31\ \x34\x35\x63\x2d\x31\x2e\x34\x31\x39\x2c\x30\x2d\x32\x2e\x38\x30\ \x31\x2c\x30\x2e\x31\x35\x32\x2d\x34\x2e\x32\x30\x31\x2c\x30\x2e\ \x32\x31\x39\x20\x20\x20\x63\x32\x2e\x36\x34\x33\x2d\x31\x31\x2e\ \x39\x37\x31\x2c\x34\x2e\x31\x31\x2d\x32\x34\x2e\x35\x32\x37\x2c\ \x34\x2e\x31\x31\x2d\x33\x37\x2e\x35\x30\x33\x63\x30\x2d\x32\x32\ \x2e\x39\x34\x2d\x34\x2e\x32\x38\x37\x2d\x34\x34\x2e\x38\x38\x2d\ \x31\x32\x2e\x37\x33\x32\x2d\x36\x35\x2e\x32\x31\x35\x6c\x2d\x31\ \x2e\x34\x33\x38\x2d\x33\x2e\x34\x34\x39\x6c\x2d\x33\x2e\x33\x31\ \x38\x2c\x31\x2e\x37\x31\x37\x20\x20\x20\x63\x2d\x32\x31\x2e\x38\ \x39\x36\x2c\x31\x31\x2e\x33\x31\x37\x2d\x35\x37\x2e\x33\x30\x35\ \x2c\x31\x37\x2e\x38\x38\x31\x2d\x38\x33\x2e\x31\x34\x37\x2c\x32\ \x30\x2e\x32\x34\x37\x6c\x2d\x32\x2e\x34\x30\x35\x2c\x30\x2e\x32\ \x31\x36\x6c\x2d\x30\x2e\x36\x30\x33\x2c\x32\x2e\x33\x33\x38\x63\ \x2d\x37\x2e\x30\x32\x31\x2c\x32\x37\x2e\x30\x38\x38\x2d\x31\x31\ \x2e\x31\x36\x32\x2c\x36\x30\x2e\x33\x31\x33\x2d\x31\x33\x2e\x35\ \x39\x34\x2c\x39\x31\x2e\x31\x35\x35\x20\x20\x20\x63\x2d\x33\x2e\ \x38\x35\x31\x2d\x34\x39\x2e\x30\x34\x32\x2d\x39\x2e\x35\x30\x38\ \x2d\x39\x30\x2e\x36\x31\x33\x2d\x39\x2e\x36\x30\x33\x2d\x39\x31\ \x2e\x32\x34\x39\x6c\x2d\x30\x2e\x34\x32\x39\x2d\x32\x2e\x39\x34\ \x37\x63\x2d\x34\x35\x2e\x30\x33\x39\x2d\x34\x2e\x38\x32\x2d\x38\ \x33\x2e\x33\x36\x31\x2d\x32\x30\x2e\x33\x37\x32\x2d\x39\x30\x2e\ \x30\x38\x2d\x32\x33\x2e\x34\x33\x35\x6c\x2d\x33\x2e\x31\x35\x37\ \x2d\x31\x2e\x34\x34\x6c\x2d\x31\x2e\x34\x31\x36\x2c\x33\x2e\x31\ \x36\x39\x20\x20\x20\x63\x2d\x39\x2e\x33\x37\x32\x2c\x32\x31\x2e\ \x30\x32\x39\x2d\x31\x34\x2e\x33\x32\x32\x2c\x34\x34\x2e\x38\x35\ \x2d\x31\x34\x2e\x33\x32\x32\x2c\x36\x38\x2e\x38\x39\x33\x63\x30\ \x2c\x37\x39\x2e\x35\x37\x39\x2c\x35\x32\x2e\x39\x38\x38\x2c\x31\ \x34\x34\x2e\x33\x31\x39\x2c\x31\x31\x38\x2e\x31\x32\x2c\x31\x34\ \x34\x2e\x33\x31\x39\x63\x38\x2e\x36\x37\x35\x2c\x30\x2c\x31\x37\ \x2e\x31\x31\x2d\x31\x2e\x32\x33\x2c\x32\x35\x2e\x32\x34\x36\x2d\ \x33\x2e\x34\x31\x20\x20\x20\x63\x35\x2e\x30\x35\x34\x2c\x34\x36\ \x2e\x38\x33\x38\x2c\x34\x34\x2e\x38\x30\x34\x2c\x38\x33\x2e\x34\ \x34\x35\x2c\x39\x32\x2e\x39\x36\x33\x2c\x38\x33\x2e\x34\x34\x35\ \x63\x35\x31\x2e\x35\x36\x39\x2c\x30\x2c\x39\x33\x2e\x35\x32\x39\ \x2d\x34\x31\x2e\x39\x36\x2c\x39\x33\x2e\x35\x32\x39\x2d\x39\x33\ \x2e\x35\x32\x39\x43\x34\x31\x32\x2e\x37\x36\x2c\x32\x36\x34\x2e\ \x31\x31\x32\x2c\x33\x37\x30\x2e\x38\x31\x32\x2c\x32\x32\x32\x2e\ \x31\x34\x35\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\ \x31\x34\x35\x7a\x20\x20\x20\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\ \x2c\x34\x30\x32\x2e\x39\x36\x38\x63\x2d\x34\x38\x2e\x31\x33\x35\ \x2c\x30\x2d\x38\x37\x2e\x32\x39\x34\x2d\x33\x39\x2e\x31\x35\x33\ \x2d\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\x32\x39\x34\x63\x30\ \x2d\x34\x38\x2e\x31\x32\x39\x2c\x33\x39\x2e\x31\x35\x39\x2d\x38\ \x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\ \x32\x39\x34\x73\x38\x37\x2e\x32\x39\x34\x2c\x33\x39\x2e\x31\x36\ \x35\x2c\x38\x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x20\ \x20\x20\x43\x34\x30\x36\x2e\x35\x33\x2c\x33\x36\x33\x2e\x38\x31\ \x35\x2c\x33\x36\x37\x2e\x33\x36\x35\x2c\x34\x30\x32\x2e\x39\x36\ \x38\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x34\x30\x32\x2e\x39\x36\ \x38\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\x46\x46\x46\ \x46\x22\x2f\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x2f\x73\x76\x67\x3e\x0a\ \x00\x00\x03\x68\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x73\x76\x67\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\x20\x76\x65\x72\ \x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\x3d\x22\x43\ \x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\x22\x20\x79\ \x3d\x22\x30\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ \x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ \x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ \x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ \x39\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ \x72\x65\x73\x65\x72\x76\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\ \x31\x32\x70\x78\x22\x3e\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\ \x4d\x33\x36\x2e\x30\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x6c\x2d\ \x32\x39\x2d\x32\x30\x43\x36\x2e\x37\x36\x31\x2d\x30\x2e\x30\x33\ \x35\x2c\x36\x2e\x33\x36\x33\x2d\x30\x2e\x30\x35\x37\x2c\x36\x2e\ \x30\x33\x35\x2c\x30\x2e\x31\x31\x34\x43\x35\x2e\x37\x30\x36\x2c\ \x30\x2e\x32\x38\x37\x2c\x35\x2e\x35\x2c\x30\x2e\x36\x32\x37\x2c\ \x35\x2e\x35\x2c\x30\x2e\x39\x39\x39\x76\x34\x30\x20\x20\x63\x30\ \x2c\x30\x2e\x33\x37\x32\x2c\x30\x2e\x32\x30\x36\x2c\x30\x2e\x37\ \x31\x33\x2c\x30\x2e\x35\x33\x35\x2c\x30\x2e\x38\x38\x36\x63\x30\ \x2e\x31\x34\x36\x2c\x30\x2e\x30\x37\x36\x2c\x30\x2e\x33\x30\x36\ \x2c\x30\x2e\x31\x31\x34\x2c\x30\x2e\x34\x36\x35\x2c\x30\x2e\x31\ \x31\x34\x63\x30\x2e\x31\x39\x39\x2c\x30\x2c\x30\x2e\x33\x39\x37\ \x2d\x30\x2e\x30\x36\x2c\x30\x2e\x35\x36\x38\x2d\x30\x2e\x31\x37\ \x37\x6c\x32\x39\x2d\x32\x30\x20\x20\x63\x30\x2e\x32\x37\x31\x2d\ \x30\x2e\x31\x38\x37\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x34\x39\ \x34\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x38\x32\x33\x53\x33\x36\ \x2e\x33\x33\x38\x2c\x32\x30\x2e\x33\x36\x33\x2c\x33\x36\x2e\x30\ \x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x7a\x20\x4d\x37\x2e\x35\x2c\ \x33\x39\x2e\x30\x39\x35\x56\x32\x2e\x39\x30\x34\x6c\x32\x36\x2e\ \x32\x33\x39\x2c\x31\x38\x2e\x30\x39\x36\x4c\x37\x2e\x35\x2c\x33\ \x39\x2e\x30\x39\x35\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\ \x46\x46\x46\x46\x46\x22\x2f\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x2f\x73\x76\x67\x3e\x0a\ \x00\x00\x02\xe2\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\ \x20\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\ \x65\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\ \x2e\x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\ \x20\x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\ \x65\x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\ \x6c\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\ \x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\ \x64\x3d\x22\x43\x61\x70\x61\x5f\x31\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\x20\x78\x3d\x22\x30\x70\x78\x22\ \x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x76\x69\x65\x77\ \x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x35\x38\x20\x35\x38\x22\x20\ \x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\x2d\x62\x61\ \x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\x30\x20\x30\ \x20\x35\x38\x20\x35\x38\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\ \x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0d\x0a\ \x3c\x63\x69\x72\x63\x6c\x65\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\ \x69\x6c\x6c\x3a\x23\x44\x37\x35\x41\x34\x41\x3b\x22\x20\x63\x78\ \x3d\x22\x32\x39\x22\x20\x63\x79\x3d\x22\x32\x39\x22\x20\x72\x3d\ \x22\x32\x39\x22\x2f\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x09\x3c\x72\ \x65\x63\x74\x20\x78\x3d\x22\x31\x36\x22\x20\x79\x3d\x22\x31\x36\ \x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x23\x46\ \x46\x46\x46\x46\x46\x3b\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\ \x36\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x36\x22\x2f\x3e\ \x0d\x0a\x09\x3c\x70\x61\x74\x68\x20\x73\x74\x79\x6c\x65\x3d\x22\ \x66\x69\x6c\x6c\x3a\x23\x46\x46\x46\x46\x46\x46\x3b\x22\x20\x64\ \x3d\x22\x4d\x34\x33\x2c\x34\x33\x48\x31\x35\x56\x31\x35\x68\x32\ \x38\x56\x34\x33\x7a\x20\x4d\x31\x37\x2c\x34\x31\x68\x32\x34\x56\ \x31\x37\x48\x31\x37\x56\x34\x31\x7a\x22\x2f\x3e\x0d\x0a\x3c\x2f\ \x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\ \x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\ \x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\ \x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\ \x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\ \x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\ \x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\ \x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\ \x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\ \x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\ \x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\ \x0a\ \x00\x00\x04\x03\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ \x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ \x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ \x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\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\x22\x3e\x0a\x3c\x73\x76\ \x67\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\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ \x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ \x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\x38\x2e\ \x38\x33\x32\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\ \x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\ \x77\x20\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\ \x38\x2e\x38\x33\x32\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\ \x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\ \x3e\x0a\x09\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x36\x35\ \x2e\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x6c\x2d\x38\x30\ \x2d\x38\x30\x63\x2d\x34\x2e\x38\x38\x31\x2d\x34\x2e\x38\x38\x31\ \x2d\x31\x32\x2e\x37\x39\x37\x2d\x34\x2e\x38\x38\x31\x2d\x31\x37\ \x2e\x36\x37\x38\x2c\x30\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\ \x38\x38\x32\x2d\x34\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\ \x2c\x30\x2c\x31\x37\x2e\x36\x37\x38\x6c\x35\x38\x2e\x36\x36\x31\ \x2c\x35\x38\x2e\x36\x36\x31\x48\x31\x32\x2e\x35\x20\x20\x20\x63\ \x2d\x36\x2e\x39\x30\x33\x2c\x30\x2d\x31\x32\x2e\x35\x2c\x35\x2e\ \x35\x39\x37\x2d\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x63\x30\x2c\ \x36\x2e\x39\x30\x32\x2c\x35\x2e\x35\x39\x37\x2c\x31\x32\x2e\x35\ \x2c\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x68\x32\x31\x33\x2e\x36\ \x35\x34\x6c\x2d\x35\x38\x2e\x36\x35\x39\x2c\x35\x38\x2e\x36\x36\ \x31\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\x38\x38\x32\x2d\x34\ \x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\x2c\x30\x2c\x31\x37\ \x2e\x36\x37\x38\x20\x20\x20\x63\x32\x2e\x34\x34\x2c\x32\x2e\x34\ \x33\x39\x2c\x35\x2e\x36\x34\x2c\x33\x2e\x36\x36\x31\x2c\x38\x2e\ \x38\x33\x39\x2c\x33\x2e\x36\x36\x31\x73\x36\x2e\x33\x39\x38\x2d\ \x31\x2e\x32\x32\x32\x2c\x38\x2e\x38\x33\x39\x2d\x33\x2e\x36\x36\ \x31\x6c\x37\x39\x2e\x39\x39\x38\x2d\x38\x30\x43\x32\x37\x30\x2e\ \x30\x35\x33\x2c\x31\x33\x38\x2e\x33\x37\x33\x2c\x32\x37\x30\x2e\ \x30\x35\x33\x2c\x31\x33\x30\x2e\x34\x35\x39\x2c\x32\x36\x35\x2e\ \x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x7a\x22\x20\x66\x69\ \x6c\x6c\x3d\x22\x23\x46\x46\x44\x41\x34\x34\x22\x2f\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\ \x3e\x0a\ \x00\x00\x03\x58\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\ \x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\x47\x65\x6e\x65\ \x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\x20\x49\x6c\x6c\ \x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\x30\x2e\x30\x2c\ \x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\x50\x6c\x75\x67\ \x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\x72\x73\x69\x6f\ \x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\x64\x20\x30\x29\ \x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ \x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ \x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ \x2f\x2f\x45\x4e\x22\x20\x22\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\x22\x3e\x0d\x0a\x3c\x73\x76\x67\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\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\x20\x78\x3d\x22\x30\x70\x78\x22\ \x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x77\x69\x64\x74\ \x68\x3d\x22\x32\x34\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ \x22\x32\x34\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\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\x32\x34\x20\x32\x34\x22\x20\x78\x6d\x6c\ \x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\ \x22\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x39\x2e\ \x34\x38\x38\x2c\x33\x2e\x39\x30\x37\x48\x32\x34\x76\x32\x2e\x37\ \x39\x31\x48\x39\x2e\x34\x38\x38\x56\x33\x2e\x39\x30\x37\x7a\x20\ \x4d\x31\x31\x2e\x31\x36\x33\x2c\x38\x2e\x33\x37\x32\x48\x32\x34\ \x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\x31\x36\x33\x56\x38\x2e\ \x33\x37\x32\x7a\x20\x4d\x31\x31\x2e\x31\x36\x33\x2c\x31\x32\x2e\ \x38\x33\x37\x48\x32\x34\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\ \x31\x36\x33\x56\x31\x32\x2e\x38\x33\x37\x7a\x0d\x0a\x09\x20\x4d\ \x39\x2e\x34\x38\x38\x2c\x31\x37\x2e\x33\x30\x32\x48\x32\x34\x76\ \x32\x2e\x37\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x31\x37\x2e\x33\ \x30\x32\x7a\x20\x4d\x32\x2e\x37\x39\x31\x2c\x31\x30\x2e\x36\x30\ \x34\x63\x30\x2c\x31\x2e\x32\x33\x33\x2c\x31\x2c\x32\x2e\x32\x33\ \x33\x2c\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\x33\x33\x48\x36\x2e\ \x31\x34\x76\x2d\x32\x2e\x32\x33\x33\x6c\x33\x2e\x37\x36\x37\x2c\ \x33\x2e\x36\x32\x38\x4c\x36\x2e\x31\x34\x2c\x31\x37\x2e\x38\x36\ \x76\x2d\x32\x2e\x32\x33\x32\x48\x35\x2e\x30\x32\x33\x0d\x0a\x09\ \x43\x32\x2e\x32\x34\x39\x2c\x31\x35\x2e\x36\x32\x38\x2c\x30\x2c\ \x31\x33\x2e\x33\x37\x39\x2c\x30\x2c\x31\x30\x2e\x36\x30\x34\x56\ \x38\x2e\x39\x33\x63\x30\x2d\x32\x2e\x37\x37\x34\x2c\x32\x2e\x32\ \x34\x39\x2d\x35\x2e\x30\x32\x33\x2c\x35\x2e\x30\x32\x33\x2d\x35\ \x2e\x30\x32\x33\x48\x36\x2e\x31\x34\x76\x32\x2e\x37\x39\x31\x48\ \x35\x2e\x30\x32\x33\x63\x2d\x31\x2e\x32\x33\x33\x2c\x30\x2d\x32\ \x2e\x32\x33\x33\x2c\x31\x2d\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\ \x33\x32\x56\x31\x30\x2e\x36\x30\x34\x7a\x22\x2f\x3e\x0d\x0a\x3c\ \x2f\x73\x76\x67\x3e\x0d\x0a\ " qt_resource_name = b"\ \x00\x05\ \x00\x6f\xa6\x53\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x73\ \x00\x09\ \x09\xba\x8f\xa7\ \x00\x64\ \x00\x65\x00\x62\x00\x75\x00\x67\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x07\ \x09\xc1\x5a\x27\ \x00\x72\ \x00\x75\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x0b\x63\x55\x87\ \x00\x73\ \x00\x74\x00\x6f\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x0f\ \x0a\x15\x41\x07\ \x00\x64\ \x00\x65\x00\x62\x00\x75\x00\x67\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x0a\xc3\x55\x87\ \x00\x73\ \x00\x74\x00\x65\x00\x70\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\x05\x00\x00\x00\x02\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xdd\ \x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x16\x2f\ \x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x36\ \x00\x00\x00\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x13\x49\ " + 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/scripter/test.py b/plugins/extensions/pykrita/plugin/plugins/scripter/test.py index 7ec3b3bcf3..13823be57e 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/test.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/test.py @@ -1,24 +1,24 @@ # editor.py from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * import syntax app = QApplication([]) editor = QPlainTextEdit() f = QFont("monospace", 10, QFont.Normal) f.setFixedPitch(True) editor.document().setDefaultFont(f) highlight = syntax.PythonHighlighter(editor.document()) editor.show() # Load syntax.py into the editor for demo purposes -#infile = open('syntax.py', 'r') -#editor.setPlainText(infile.read()) +# infile = open('syntax.py', 'r') +# editor.setPlainText(infile.read()) app.exec_() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py index 3a4d918f86..8769d6540a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py @@ -1,37 +1,37 @@ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class CloseAction(QAction): def __init__(self, scripter, parent=None): super(CloseAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.close) self.setText('Close') self.setObjectName('close') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q)) @property def parent(self): return 'File' def close(self): msgBox = QMessageBox(self.scripter.uicontroller.mainWidget) - msgBox.setInformativeText("Do you want to save the current document?"); - msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel); - msgBox.setDefaultButton(QMessageBox.Save); + msgBox.setInformativeText("Do you want to save the current document?") + msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Save) - ret = msgBox.exec(); + ret = msgBox.exec() if ret == QMessageBox.Cancel: return if ret == QMessageBox.Save: if not self.scripter.uicontroller.invokeAction('save'): return self.scripter.uicontroller.closeScripter() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py index e2f1798bac..e8e516792d 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py @@ -1,39 +1,39 @@ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class NewAction(QAction): def __init__(self, scripter, parent=None): super(NewAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.new) self.setText('New') self.setObjectName('new') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_N)) @property def parent(self): return 'File' def new(self): msgBox = QMessageBox(self.scripter.uicontroller.mainWidget) - msgBox.setText("The document has been modified."); - msgBox.setInformativeText("Do you want to save your changes?"); - msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel); - msgBox.setDefaultButton(QMessageBox.Save); + msgBox.setText("The document has been modified.") + msgBox.setInformativeText("Do you want to save your changes?") + msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Save) - ret = msgBox.exec(); + ret = msgBox.exec() if ret == QMessageBox.Cancel: return if ret == QMessageBox.Save: self.scripter.uicontroller.invokeAction('save') self.scripter.documentcontroller.clearActiveDocument() self.scripter.uicontroller.setStatusBar() self.scripter.uicontroller.clearEditor() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py index 5b2347ba6e..393478e23a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py @@ -1,38 +1,38 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class OpenAction(QAction): def __init__(self, scripter, parent=None): super(OpenAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.open) self.setText('Open') self.setObjectName('open') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_O)) @property def parent(self): return 'File' def open(self): dialog = QFileDialog(self.scripter.uicontroller.mainWidget) dialog.setNameFilter('Python files (*.py)') if dialog.exec(): try: selectedFile = dialog.selectedFiles()[0] fileExtension = selectedFile.rsplit('.', maxsplit=1)[1] - if fileExtension=='py': + if fileExtension == 'py': document = self.scripter.documentcontroller.openDocument(selectedFile) self.scripter.uicontroller.setDocumentEditor(document) self.scripter.uicontroller.setStatusBar(document.filePath) except: QMessageBox.information(self.scripter.uicontroller.mainWidget, 'Invalid File', 'Open files with .py extension') diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py index 002b3f5f89..a124b52f7f 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py @@ -1,12 +1,13 @@ from PyQt5.QtGui import QTextCursor + class DocWrapper: def __init__(self, textdocument): self.textdocument = textdocument - def write(self, text, view = None): + def write(self, text, view=None): cursor = QTextCursor(self.textdocument) cursor.clearSelection() cursor.movePosition(QTextCursor.End) cursor.insertText(text) 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/actions/saveaction/saveaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py index eb69823d50..f02cfa00a4 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py @@ -1,50 +1,50 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class SaveAction(QAction): def __init__(self, scripter, parent=None): super(SaveAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.triggered.connect(self.save) self.setText('Save') self.setObjectName('save') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) @property def parent(self): return 'File' def save(self): text = self.editor.toPlainText() fileName = '' fileExtension = '' if not self.scripter.documentcontroller.activeDocument: try: fileName = QFileDialog.getSaveFileName(self.scripter.uicontroller.mainWidget, 'Save Python File', '', 'Python File (*.py)')[0] if not fileName: return fileExtension = fileName.rsplit('.', maxsplit=1)[1] except: - if not fileExtension=='py': + if not fileExtension == 'py': QMessageBox.information(self.scripter.uicontroller.mainWidget, 'Invalid File', 'Save files with .py extension') return document = self.scripter.documentcontroller.saveDocument(text, fileName) if document: self.scripter.uicontroller.setStatusBar(document.filePath) else: self.scripter.uicontroller.setStatusBar('untitled') return document diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py index e34aef5525..e5f1f7188a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py @@ -1,151 +1,150 @@ from PyQt5.QtCore import Qt, QRect, QSize, QPoint from PyQt5.QtWidgets import QPlainTextEdit, QTextEdit from PyQt5.QtGui import QIcon, QColor, QPainter, QTextFormat, QFont, QTextCursor from scripter.ui_scripter.editor import linenumberarea, debugarea from scripter import resources_rc class CodeEditor(QPlainTextEdit): DEBUG_AREA_WIDTH = 20 def __init__(self, scripter, parent=None): super(CodeEditor, self).__init__(parent) self.setLineWrapMode(self.NoWrap) self.scripter = scripter self.lineNumberArea = linenumberarea.LineNumberArea(self) self.debugArea = debugarea.DebugArea(self) self.blockCountChanged.connect(self.updateMarginsWidth) self.updateRequest.connect(self.updateLineNumberArea) self.cursorPositionChanged.connect(self.highlightCurrentLine) self.updateMarginsWidth() self.highlightCurrentLine() self.font = "Monospace" self._stepped = False self.debugArrow = QIcon(':/icons/debug_arrow.svg') - def debugAreaWidth(self): return self.DEBUG_AREA_WIDTH def lineNumberAreaWidth(self): """The lineNumberAreaWidth is the quatity of decimal places in blockCount""" digits = 1 max_ = max(1, self.blockCount()) while (max_ >= 10): max_ /= 10 digits += 1 space = 3 + self.fontMetrics().width('9') * digits return space def resizeEvent(self, event): super(CodeEditor, self).resizeEvent(event) qRect = self.contentsRect() self.debugArea.setGeometry(QRect(qRect.left(), qRect.top(), self.debugAreaWidth(), qRect.height())) self.lineNumberArea.setGeometry(QRect(qRect.left() + self.debugAreaWidth(), - qRect.top(), - self.lineNumberAreaWidth(), - qRect.height())) + qRect.top(), + self.lineNumberAreaWidth(), + qRect.height())) def updateMarginsWidth(self): self.setViewportMargins(self.lineNumberAreaWidth() + self.debugAreaWidth(), 0, 0, 0) def updateLineNumberArea(self, rect, dy): """ This slot is invoked when the editors viewport has been scrolled """ if dy: self.lineNumberArea.scroll(0, dy) self.debugArea.scroll(0, dy) else: self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateMarginsWidth() def lineNumberAreaPaintEvent(self, event): """This method draws the current lineNumberArea for while""" painter = QPainter(self.lineNumberArea) painter.fillRect(event.rect(), QColor(Qt.lightGray).darker(300)) block = self.firstVisibleBlock() blockNumber = block.blockNumber() top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) while block.isValid() and top <= event.rect().bottom(): if block.isVisible() and bottom >= event.rect().top(): number = str(blockNumber + 1) painter.setPen(QColor(Qt.lightGray)) painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(), Qt.AlignRight, number) block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) blockNumber += 1 def debugAreaPaintEvent(self, event): if self.scripter.debugcontroller.isActive and self.scripter.debugcontroller.currentLine: lineNumber = self.scripter.debugcontroller.currentLine - block = self.document().findBlockByLineNumber(lineNumber-1) + block = self.document().findBlockByLineNumber(lineNumber - 1) if self._stepped: cursor = QTextCursor(block) self.setTextCursor(cursor) self._stepped = False top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) painter = QPainter(self.debugArea) - pixmap = self.debugArrow.pixmap(QSize(self.debugAreaWidth()-3, int(self.blockBoundingRect(block).height()))) + pixmap = self.debugArrow.pixmap(QSize(self.debugAreaWidth() - 3, int(self.blockBoundingRect(block).height()))) painter.drawPixmap(QPoint(0, top), pixmap) def highlightCurrentLine(self): """Highlight current line under cursor""" currentSelection = QTextEdit.ExtraSelection() lineColor = QColor(Qt.gray).darker(250) currentSelection.format.setBackground(lineColor) currentSelection.format.setProperty(QTextFormat.FullWidthSelection, True) currentSelection.cursor = self.textCursor() currentSelection.cursor.clearSelection() self.setExtraSelections([currentSelection]) def wheelEvent(self, e): """When the CTRL is pressed during the wheelEvent, zoomIn and zoomOut slots are invoked""" if e.modifiers() == Qt.ControlModifier: delta = e.angleDelta().y() if delta < 0: self.zoomOut() elif delta > 0: self.zoomIn() else: super(CodeEditor, self).wheelEvent(e) @property def font(self): return self._font @font.setter def font(self, font): self._font = font self.setFont(QFont(font, 10)) def setStepped(self, status): self._stepped = status def repaintDebugArea(self): self.debugArea.repaint() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py index 7519ff7e87..97abf68ae8 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py @@ -1,156 +1,155 @@ # syntax.py: taken from https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting import sys from PyQt5.QtCore import QRegExp from PyQt5.QtGui import QSyntaxHighlighter class PythonHighlighter (QSyntaxHighlighter): + """Syntax highlighter for the Python language. """ # Python keywords keywords = [ 'and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'yield', 'None', 'True', 'False', ] # Python operators operators = [ '=', # Comparison '==', '!=', '<', '<=', '>', '>=', # Arithmetic '\+', '-', '\*', '/', '//', '\%', '\*\*', # In-place '\+=', '-=', '\*=', '/=', '\%=', # Bitwise '\^', '\|', '\&', '\~', '>>', '<<', ] # Python braces braces = [ '\{', '\}', '\(', '\)', '\[', '\]', ] def __init__(self, document, syntaxStyle): QSyntaxHighlighter.__init__(self, document) self.syntaxStyle = syntaxStyle self.document = document # Multi-line strings (expression, flag, style) # FIXME: The triple-quotes in these two lines will mess up the # syntax highlighting from this point onward self.tri_single = (QRegExp(r"""'''(?!")"""), 1, 'string2') self.tri_double = (QRegExp(r'''"""(?!')'''), 2, 'string2') rules = [] # Keyword, operator, and brace rules rules += [(r'\b%s\b' % w, 0, 'keyword') - for w in PythonHighlighter.keywords] + for w in PythonHighlighter.keywords] rules += [(r'%s' % o, 0, 'operator') - for o in PythonHighlighter.operators] + for o in PythonHighlighter.operators] rules += [(r'%s' % b, 0, 'brace') - for b in PythonHighlighter.braces] + for b in PythonHighlighter.braces] # All other rules rules += [ # 'self' (r'\bself\b', 0, 'self'), # Double-quoted string, possibly containing escape sequences (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'), # Single-quoted string, possibly containing escape sequences (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'), # 'def' followed by an identifier (r'\bdef\b\s*(\w+)', 1, 'defclass'), # 'class' followed by an identifier (r'\bclass\b\s*(\w+)', 1, 'defclass'), # From '#' until a newline (r'#[^\n]*', 0, 'comment'), # Numeric literals (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'), ] # Build a QRegExp for each pattern self.rules = [(QRegExp(pat), index, identifier) - for (pat, index, identifier) in rules] - + for (pat, index, identifier) in rules] def highlightBlock(self, text): """Apply syntax highlighting to the given block of text.""" # Do other syntax formatting for expression, nth, identifier in self.rules: index = expression.indexIn(text, 0) while index >= 0: # We actually want the index of the nth match index = expression.pos(nth) length = len(expression.cap(nth)) self.setFormat(index, length, self.syntaxStyle[identifier]) index = expression.indexIn(text, index + length) self.setCurrentBlockState(0) # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double) - def match_multiline(self, text, delimiter, in_state, style): """Do highlighting of multi-line strings. ``delimiter`` should be a ``QRegExp`` for triple-single-quotes or triple-double-quotes, and ``in_state`` should be a unique integer to represent the corresponding state changes when inside those strings. Returns True if we're still inside a multi-line string when this function is finished. """ # If inside triple-single quotes, start at 0 if self.previousBlockState() == in_state: start = 0 add = 0 # Otherwise, look for the delimiter on this line else: start = delimiter.indexIn(text) # Move past this match add = delimiter.matchedLength() # As long as there's a delimiter match on this line... while start >= 0: # Look for the ending delimiter end = delimiter.indexIn(text, start + add) # Ending delimiter on this line? if end >= add: length = end - start + add + delimiter.matchedLength() self.setCurrentBlockState(0) # No; multi-line string else: self.setCurrentBlockState(in_state) length = len(text) - start + add # Apply formatting self.setFormat(start, length, self.syntaxStyle[style]) # Look for the next match start = delimiter.indexIn(text, start + length) # Return True if still inside a multi-line string, False otherwise if self.currentBlockState() == in_state: return True else: return False def getSyntaxStyle(self): return self.syntaxStyle def setSyntaxStyle(self, syntaxStyle): self.syntaxStyle = syntaxStyle diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py index 3aae750bd4..ee1de59309 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py @@ -1,56 +1,57 @@ from PyQt5.QtGui import QColor, QTextCharFormat, QFont def format(color, style='', darker=100, lighter=100): """Return a QTextCharFormat with the given attributes. """ _color = QColor(color) _color = _color.darker(darker) _color = _color.lighter(lighter) _format = QTextCharFormat() _format.setForeground(_color) if 'bold' in style: _format.setFontWeight(QFont.Bold) if 'italic' in style: _format.setFontItalic(True) return _format class DefaultSyntaxStyle(object): # Syntax styles that combines with dark backgrounds STYLES = { 'keyword': format('cyan'), 'operator': format('orange'), 'brace': format('gray'), 'defclass': format('black', 'bold'), 'string': format('magenta'), 'string2': format('darkMagenta'), 'comment': format('darkGreen', 'italic'), 'self': format('black', 'italic'), 'numbers': format('brown'), } def __getitem__(self, key): return self.STYLES[key] class PythonVimSyntaxStyle(object): + """ It based in the colorschemme of the Vim editor for python code http://www.vim.org/scripts/script.php?script_id=790 """ # Syntax styles that combines with dark backgrounds STYLES = { 'keyword': format('yellow', darker=125), 'operator': format('magenta', darker=150), 'brace': format('white'), 'defclass': format('orange', 'bold'), 'string': format('green', lighter=160), 'string2': format('lightGray', 'italic', darker=120), 'comment': format('gray', 'italic'), 'self': format('blue', lighter=170), 'numbers': format('yellow', lighter=130), } def __getitem__(self, key): return self.STYLES[key] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py index c559d7e342..560555532c 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py @@ -1,2 +1,2 @@ widgetClasses = ['outputwidget.outputwidget.OutPutWidget', - 'debuggerwidget.debuggerwidget.DebuggerWidget',] + 'debuggerwidget.debuggerwidget.DebuggerWidget', ] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py index f1c1445bfe..f107e76d4b 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py @@ -1,33 +1,33 @@ from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem class DebuggerTable(QTableWidget): def __init__(self, parent=None): super(DebuggerTable, self).__init__(parent) self.setColumnCount(4) tableHeader = ['Scope', 'Name', 'Value', 'Type'] self.setHorizontalHeaderLabels(tableHeader) self.setEditTriggers(self.NoEditTriggers) def updateTable(self, data): self.clearContents() self.setRowCount(0) if data and not data.get('quit') and not data.get('exception'): locals_list = data['frame']['locals'] globals_list = data['frame']['globals'] all_variables = {'locals': locals_list, 'globals': globals_list} for scope_key in all_variables: for item in all_variables[scope_key]: for key, value in item.items(): - row = self.rowCount() + row = self.rowCount() self.insertRow(row) self.setItem(row, 0, QTableWidgetItem(str(scope_key))) self.setItem(row, 1, QTableWidgetItem(key)) self.setItem(row, 2, QTableWidgetItem(value['value'])) self.setItem(row, 3, QTableWidgetItem(value['type'])) 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..88fff8933b --- /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 ff9daec7ac..c11d074fe8 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py @@ -1,190 +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) + _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) + _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/selectionsbagdocker/selectionsbagdocker.py b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py index ec73b3f554..52b33a8a0e 100644 --- a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py +++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py @@ -1,18 +1,20 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5 import uic from krita import * import os + class SelectionsBagDocker(DockWidget): - def __init__(self): - super().__init__() - widget = QWidget(self) - uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/selectionsbagdocker.ui', widget) - self.setWidget(widget) - self.setWindowTitle("Selections bag") - def canvasChanged(self, canvas): - print("Canvas", canvas) + def __init__(self): + super().__init__() + widget = QWidget(self) + uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/selectionsbagdocker.ui', widget) + self.setWidget(widget) + self.setWindowTitle("Selections bag") + + def canvasChanged(self, canvas): + print("Canvas", canvas) 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 09476c168d..89286b2bf4 100644 --- a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py +++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py @@ -1,113 +1,58 @@ -import sys -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from krita import * +import krita +from tenbrushes import uitenbrushes -class DropButton(QPushButton): - def __init__(self, parent): - super().__init__(parent) - self.setFixedSize(64, 64) - self.setIconSize(QSize(64, 64)) - self.preset = None - - def selectPreset(self): - self.preset = self.presetChooser.currentPreset().name() - self.setIcon(QIcon(QPixmap.fromImage(self.presetChooser.currentPreset().image()))) - - -class TenBrushesExtension(Extension): +class TenBrushesExtension(krita.Extension): def __init__(self, parent): - super().__init__(parent) - self.buttons = [] + super(TenBrushesExtension, self).__init__(parent) + 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 + self.actions.append(action) 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..b85198d103 --- /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..d941604824 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()) + if (!s_pythonLibrary->load()) { errScript << "Could not load" << PYKRITA_PYTHON_LIBRARY; + 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 7ae88cf501..f32fc47b9a 100644 --- a/plugins/extensions/pykrita/sip/krita/Palette.sip +++ b/plugins/extensions/pykrita/sip/krita/Palette.sip @@ -1,37 +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/testapi.py b/plugins/extensions/pykrita/testapi.py index a647187534..6cca36f594 100644 --- a/plugins/extensions/pykrita/testapi.py +++ b/plugins/extensions/pykrita/testapi.py @@ -1,31 +1,31 @@ # # Tests the PyKrita API # import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + def __main__(args): print("Arguments:", args) Application.setBatchmode(True) print("Batchmode: ", Application.batchmode()) - print("Profiles:", Application.profiles("GRAYA", "U16")); + print("Profiles:", Application.profiles("GRAYA", "U16")) document = Application.openDocument(args[0]) print("Opened", document.fileName(), "WxH", document.width(), document.height(), "resolution", document.xRes(), document.yRes(), "in ppi", document.resolution()) node = document.rootNode() print("Root", node.name(), "opacity", node.opacity()) for child in node.childNodes(): print("\tChild", child.name(), "opacity", node.opacity(), node.blendingMode()) - #r = child.save(child.name() + ".png", document.xRes(), document.yRes()); - #print("Saving result:", r) + # r = child.save(child.name() + ".png", document.xRes(), document.yRes()); + # print("Saving result:", r) for channel in child.channels(): print("Channel", channel.name(), "contents:", len(channel.pixelData(node.bounds()))) document.close() - + document = Application.createDocument(100, 100, "test", "GRAYA", "U16", "") document.setBatchmode(True) - #document.saveAs("test.kra") - + # document.saveAs("test.kra") 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..d3ffa65dfc --- /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..1050cc3dc9 --- /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..8011b77e69 --- /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/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_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_