diff --git a/3rdparty/README.md b/3rdparty/README.md index b747d7b77b..b0acc80f6c 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,289 +1,289 @@ # 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 build-tools/windows/build.cmd or KDE's craft * you develop on OSX and aren't using the scripts in krita/packaging/osx or Homebrew * you want to build a generic, distro-agnostic version of Krita for Linux and aren't using the scripts in packaging/linux/appimage * you develop on Linux, but some dependencies aren't available for your distribution and aren't using the scripts in packaging/linux/appimage and you know what you're doing, you can use the following guide to build the dependencies that Krita needs. Using the scripts mentioned above is strongly preferred because that's what the Krita team uses to build the binaries on the binary factory (https://binary-factory.kde.org/). 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. * CMake 3.9 does not build Krita properly at the moment, please use 3.8 or 3.10 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 7.3 (by mingw-builds): https://files.kde.org/krita/build/x86_64-7.3.0-release-posix-seh-rt_v5-rev0.7z * For threading, select posix. * For exceptions, select seh (64-bit) or dwarf (32-bit). * Install mingw to something like C:\mingw; the full path must not contain any spaces. * 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 a release of Python 3.9: 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 that this version of Python comes first in your path. Do not set PYTHONHOME or PYTHONPATH. +4. On Windows, you will also need a release of Python 3.8 (not 3.7, probably not 3.9): 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 that this version of Python comes first in your path. Do not set PYTHONHOME or PYTHONPATH. * Make sure that your Python will have the correct architecture for the version you are trying to build. If building for 32-bit target, you need the 32-bit release of Python. 5. On Windows, if you want to compile Qt with ANGLE support, you will need to install Windows 10 SDK and have 2 environment variables set: * `WindowsSdkDir` (typically set to `C:\Program Files (x86)\Windows Kits\10`) * `WindowsSdkVerBinPath` (typically set to `C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0`) where 10.0.18362.0 is the version of the Window 10 SDK. * Example: set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" set "WindowsSdkVerBinPath=%ProgramFiles(x86)%\Windows Kits\10\bin\10.0.18362.0" THIS IS ALSO NEEDED IF YOU USE THE build.cmd script. ## 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 a bare 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 ``` * 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% 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 this 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). For other jobs, you might need to manually add a -- -j N option, where N is the number of jobs. - If you don't have Windows 10 SDK and don't want to build Qt with ANGLE, add `-DQT_ENABLE_DYNAMIC_OPENGL=OFF` to the CMake command line args. 3. Build the packages: On Windows: ``` cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico ``` On OSX and Windows: ``` cmake --build . --config RelWithDebInfo --target ext_gettext cmake --build . --config RelWithDebInfo --target ext_openssl ``` 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 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 ``` OSX Note: 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 cmake --build . --config RelWithDebInfo --target ext_giflib cmake --build . --config RelWithDebInfo --target ext_openjpeg cmake --build . --config RelWithDebInfo --target ext_quazip ``` On Linux (if you want to build your own SIP and PyQt instead of the system one) ``` cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt ``` 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 ``` 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 ``` Troubleshooting: if you have problems with 'install' step of ext_sip or ext_pyqt, make sure you install it in single thread only (`mingw32-make -j1 install`). Otherwise, a race condition may happen in the post-install script and metadata generation will be started before actual libraries are installed. On Windows and Linux (if you want to include gmic-qt) ``` cmake --build . --config RelWithDebInfo --target ext_gmic ``` Linux 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. OSX Note: In order to build fontconfig on macOS, you need to have pkg-config installed. You probably need homebrew for that... See http://macappstore.org/pkg-config/ . archives from: files.kde.org/krita/build/dependencies: On Windows and OSX ``` cmake --build . --config RelWithDebInfo --target ext_kwindowsystem ``` ## Build Krita 1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build 3. Configure the build: On Windows ``` 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=RelWithDebInfo # Troubleshooting: if you built your own SIP and CMake fails to find it, please set # the following environment variable to the SIP installation directory: export PYTHONPATH=$BUILDROOT/i/sip/ # If you also have KIO installed in the system, don't forget to disable it by bassing to cmake: # cmake -DCMAKE_DISABLE_FIND_PACKAGE_KF5KIO=true . ``` 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 the build: On Linux and OSX ``` make make install ``` On Windows (replace 4 with the number of jobs to run in parallel) ``` cmake --build . --target install -- -j4 ``` 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 run the packaging/windows/package-complete.cmd script. That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version. diff --git a/3rdparty/ext_frameworks/CMakeLists.txt b/3rdparty/ext_frameworks/CMakeLists.txt index e56639ff72..c4307181d9 100644 --- a/3rdparty/ext_frameworks/CMakeLists.txt +++ b/3rdparty/ext_frameworks/CMakeLists.txt @@ -1,226 +1,243 @@ SET(EXTPREFIX_frameworks "${EXTPREFIX}" ) # # All needed frameworks: # # Config # WidgetsAddons # Completion # CoreAddons # GuiAddons # I18n # ItemModels # ItemViews # WindowSystem # kimageformats # On Linux: # KCrash ExternalProject_Add( ext_extra_cmake_modules DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/extra-cmake-modules-5.64.0.zip URL_MD5 e8fa4bba6a534feb9d9e39db036923da 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_kconfig DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kconfig-5.64.0.zip URL_MD5 52858f7fdcd17d0c5680acf96dcaae9a 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 -DKCONFIG_USE_DBUS=off UPDATE_COMMAND "" DEPENDS ext_extra_cmake_modules ) +ExternalProject_Add( + ext_karchive + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://download.kde.org/stable/frameworks/5.64/karchive-5.64.0.zip + URL_MD5 26c7f89bfde97bc85f887f81a2d0f648 + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/karchive.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_extra_cmake_modules +) + + ExternalProject_Add( ext_kwidgetsaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kwidgetsaddons-5.64.0.zip URL_MD5 d6578f8b5b4bf19fddb8c16918ec0117 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 https://download.kde.org/stable/frameworks/5.64/kcompletion-5.64.0.zip URL_MD5 21fef280c2580da83df289707326d42a 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 https://download.kde.org/stable/frameworks/5.64/kcoreaddons-5.64.0.zip URL_MD5 670a74cda110da89d1a7b0fd832fca42 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/desktoptojson.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 https://download.kde.org/stable/frameworks/5.64/kguiaddons-5.64.0.zip URL_MD5 565dcb39e812463100dddaf3f02bd2a0 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 ) if(APPLE) ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/ki18n-5.64.0.zip URL_MD5 75a86675bf2b352b53cbcaece956b486 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND 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 ext_gettext ) else() ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/ki18n-5.64.0.zip URL_MD5 75a86675bf2b352b53cbcaece956b486 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND 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 ) endif() ExternalProject_Add( ext_kitemmodels DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kitemmodels-5.64.0.zip URL_MD5 e561031cafe7af08de3c62f01fa154b2 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 https://download.kde.org/stable/frameworks/5.64/kitemviews-5.64.0.zip URL_MD5 bbd20e563e3f3cbc80252ba89d27962c 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 https://download.kde.org/stable/frameworks/5.64/kimageformats-5.64.0.zip URL_MD5 744090cf90ddde84f51dcabd167dd607 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kimageformats.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/disable_exr.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 + DEPENDS ext_karchive ext_kitemviews ) ExternalProject_Add( ext_kwindowsystem DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kwindowsystem-5.64.0.zip URL_MD5 4e2819edb9d029ff33791d691637065a 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 https://download.kde.org/stable/frameworks/5.64/kcrash-5.64.0.zip URL_MD5 2d4d9c074c2e1d19804618d3f291a568 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/karchive.diff b/3rdparty/ext_frameworks/karchive.diff new file mode 100644 index 0000000000..e5ce615438 --- /dev/null +++ b/3rdparty/ext_frameworks/karchive.diff @@ -0,0 +1,21 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 45b3bf0..6afd3cb 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -33,7 +33,7 @@ find_package(BZip2) + set_package_properties(BZip2 PROPERTIES + URL "https://sourceware.org/bzip2/" + DESCRIPTION "Support for BZip2 compressed files and data streams" +- TYPE RECOMMENDED ++ TYPE OPTIONAL + PURPOSE "Support for BZip2 compressed files and data streams" + ) + +@@ -41,6 +41,7 @@ find_package(LibLZMA) + set_package_properties(LibLZMA PROPERTIES + URL "http://tukaani.org/xz/" + DESCRIPTION "Support for xz compressed files and data streams" ++ TYPE OPTIONAL + PURPOSE "Support for xz compressed files and data streams" + ) + include_directories( diff --git a/3rdparty/ext_lcms2/CMakeLists.txt b/3rdparty/ext_lcms2/CMakeLists.txt index 1863b373d5..53bbedd5a5 100644 --- a/3rdparty/ext_lcms2/CMakeLists.txt +++ b/3rdparty/ext_lcms2/CMakeLists.txt @@ -1,39 +1,39 @@ SET(PREFIX_ext_lcms2 "${EXTPREFIX}" ) if (MINGW) ExternalProject_Add( ext_lcms2 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/lcms2-2.10.tar.gz URL_MD5 c5f915d681325e0767e40187799f23b1 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/lcms2-10.diff INSTALL_DIR ${PREFIX_ext_lcms2} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE -DBUILD_PLUGINS=FALSE UPDATE_COMMAND "" DEPENDS ext_patch ) elseif (ANDROID) ExternalProject_Add( ext_lcms2 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/lcms2-2.10.tar.gz URL_MD5 c5f915d681325e0767e40187799f23b1 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/lcms2-10.diff INSTALL_DIR ${PREFIX_ext_lcms2} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE -DBUILD_PLUGINS=FALSE UPDATE_COMMAND "" ) else () ExternalProject_Add( ext_lcms2 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/lcms2-2.10.tar.gz URL_MD5 c5f915d681325e0767e40187799f23b1 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/lcms2-10.diff INSTALL_DIR ${PREFIX_ext_lcms2} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE -DBUILD_PLUGINS=FALSE UPDATE_COMMAND "" ) endif () diff --git a/krita/data/workspaces/Animation.kws b/krita/data/workspaces/Animation.kws index 50b6a3d525..11ef1424df 100644 --- a/krita/data/workspaces/Animation.kws +++ b/krita/data/workspaces/Animation.kws @@ -1,6 +1,7 @@ - + - + + diff --git a/krita/krita.action b/krita/krita.action index 04e324e657..dac03247d6 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3706 +1,3578 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Show Krita log for bug reports. Show Krita log for bug reports. Show Krita log for bug reports. false Show system information for bug reports. Show system information for bug reports. Show system information for bug reports. false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale 1 0 true &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Create Snapshot Create Snapshot 1 0 false Switch to Selected Snapshot Switch to selected snapshot 1 0 false Remove Selected Snapshot Remove Selected Snapshot 1 0 false Painting lightness-increase Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false lightness-decrease Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false opacity-increase Increase opacity Increase opacity Increase opacity 0 0 O false opacity-decrease Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 0 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 0 true Hide Mirror X Line Hide Mirror X Line Hide Mirror X Line 10000 true Hide Mirror Y Line Hide Mirror Y Line Hide Mirror Y Line 10000 true Lock Lock X Line Lock X Line 10000 true Lock Y Line Lock Y Line Lock Y Line 10000 true Move to Canvas Center X Move to Canvas Center X Move to Canvas Center X 10000 false Move to Canvas Center Y Move to Canvas Center Y Move to Canvas Center Y 10000 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false Selection Mode: Add Selection Mode: Add Selection Mode: Add A false Selection Mode: Subtract Selection Mode: Subtract Selection Mode: Subtract S false Selection Mode: Intersect Selection Mode: Intersect Selection Mode: Intersect false Selection Mode: Replace Selection Mode: Replace Selection Mode: Replace R false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false brushsize-decrease Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false brushsize-increase Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Snap To Assistants Toggle Snap to Assistants ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Invert Invert Invert 10000 0 Ctrl+I false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance... Color Balance Color Balance 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false - Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false - - - Crop Tool - - Crop the image to an area - Crop the image to an area - C - false - - - - - Polygon Tool - - Polygon Tool. Shift-mouseclick ends the polygon. - Polygon Tool. Shift-mouseclick ends the polygon. - - false - - Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Colorize Mask Tool Colorize Mask Tool Colorize Mask Tool Smart Patch Tool Smart Patch Tool Smart Patch Tool Pan Tool Pan Tool Pan Tool Select Shapes Tool Select Shapes Tool Select Shapes Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false - - - Outline Selection Tool - - Outline Selection Tool - Outline Selection Tool - - false - - - - - Bezier Curve Selection Tool - - Bezier Curve Selection Tool - Bezier Curve Selection Tool - - false - - - - - Similar Color Selection Tool - - Similar Color Selection Tool - Similar Color Selection Tool - - false - - Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false - - - Freehand Path Tool - - Freehand Path Tool - Freehand Path Tool - - false - - - - - Bezier Curve Tool - - Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. - Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. - - false - - Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false - - - Elliptical Selection Tool - - Elliptical Selection Tool - Elliptical Selection Tool - J - false - - - - - Contiguous Selection Tool - - Contiguous Selection Tool - Contiguous Selection Tool - - false - - Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false - - - Polygonal Selection Tool - - Polygonal Selection Tool - Polygonal Selection Tool - - false - - Measurement Tool Measure the distance between two points Measure the distance between two points false - - - Rectangular Selection Tool - - Rectangular Selection Tool - Rectangular Selection Tool - Ctrl+R - false - - Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Edit Shapes Tool Edit Shapes Tool Edit Shapes Tool false Zoom Tool Zoom Tool Zoom Tool false - - - Polyline Tool - - Polyline Tool. Shift-mouseclick ends the polyline. - Polyline Tool. Shift-mouseclick ends the polyline. - - false - - - - - Transform Tool - - Transform a layer or a selection - Transform a layer or a selection - Ctrl+T - false - - - - - Assistant Tool - - Assistant Tool - Assistant Tool - - false - - Gradient Editing Tool Gradient editing Gradient editing false Reference Images Tool Reference Images Tool Reference Images Tool false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation - + prevframe Previous frame Move to previous frame Move to previous frame 1 0 false - + nextframe Next frame Move to next frame Move to next frame 1 0 false - + animation_play Play / pause animation Play / pause animation Play / pause animation 1 0 false + + animation_stop + Stop animation + + Stop animation + Stop animation + 1 + 0 + + false + + addblankframe Create Blank Frame Add blank frame Add blank frame 100000 0 false addduplicateframe Create Duplicate Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false - + Auto Frame Mode true - - + dropframe + Drop Frames +Enable to preserve playback timing. true Pin to Timeline If checked, layer becomes pinned to the timeline, making it visible even when inactive. true Insert Keyframe Left Insert keyframes to the left of selection, moving the tail of animation to the right. 100000 0 false Insert Keyframe Right Insert keyframes to the right of selection, moving the tail of animation to the right. 100000 0 false Insert Multiple Keyframes Insert several keyframes based on user parameters. 100000 0 false Remove Frame and Pull Remove keyframes moving the tail of animation to the left 100000 0 false deletekeyframe Remove Keyframe Remove keyframes without moving anything around 100000 0 false Insert Column Left Insert column to the left of selection, moving the tail of animation to the right 100000 0 false Insert Column Right Insert column to the right of selection, moving the tail of animation to the right 100000 0 false Insert Multiple Columns Insert several columns based on user parameters. 100000 0 false Remove Column and Pull Remove columns moving the tail of animation to the left 100000 0 false Remove Column Remove columns without moving anything around 100000 0 false Insert Hold Frame Insert a hold frame after every keyframe 100000 0 false Insert Multiple Hold Frames Insert N hold frames after every keyframe 100000 0 false Remove Hold Frame Remove a hold frame after every keyframe 100000 0 false Remove Multiple Hold Frames Remove N hold frames after every keyframe 100000 0 false Insert Hold Column Insert a hold column into the frame at the current position 100000 0 false Insert Multiple Hold Columns Insert N hold columns into the frame at the current position 100000 0 false Remove Hold Column Remove a hold column from the frame at the current position 100000 0 false Remove Multiple Hold Columns Remove N hold columns from the frame at the current position 100000 0 false Add opacity keyframe Adds keyframe to control layer opacity 100000 0 false Remove opacity keyframe Removes keyframe to control layer opacity 100000 0 false Mirror Frames Mirror frames' position 100000 0 false Mirror Columns Mirror columns' position 100000 0 false Copy to Clipboard Copy frames to clipboard 100000 0 false Cut to Clipboard Cut frames to clipboard 100000 0 false Paste from Clipboard Paste frames from clipboard 100000 0 false Copy Columns to Clipboard Copy columns to clipboard 100000 0 false Cut Columns to Clipboard Cut columns to clipboard 100000 0 false Paste Columns from Clipboard Paste columns from clipboard 100000 0 false Set Start Time 100000 0 false Set End Time 100000 0 false Update Playback Range 100000 0 false Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate next sibling layer, skipping over groups. Activate next sibling layer Activate next sibling layer 1000 0 false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previous sibling layer, skipping over groups. Activate previous sibling layer Activate previous sibling layer 1000 0 false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Active Layer Isolate Active Layer Isolate Active Layer 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 1000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 1000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 1000 0 false Quick Group Create a group layer containing selected layers Quick Group 1000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save Vector Layer as SVG... Save Vector Layer as SVG Save Vector Layer as SVG 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false Convert to &animated layer Convert layer into animation frames Convert layer into animation frames 100000 0 false fileLayer to &File Layer Saves out the layers into a new image and then references that image. Convert to File Layer 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false symmetry-horizontal Mirror All Layers Hori&zontally Mirror All Layers Horizontally Mirror All Layers Horizontally 1000 1 false symmetry-vertical Mirror All Layers &Vertically Mirror All Layers Vertically Mirror All Layers Vertically 1000 1 false &Rotate All Layers... Rotate All Layers Rotate All Layers 1000 1 false object-rotate-right Rotate All &Layers 90° to the Right Rotate All Layers 90° to the Right Rotate All Layers 90° to the Right 1000 1 false object-rotate-left Rotate All Layers &90° to the Left Rotate All Layers 90° to the Left Rotate All Layers 90° to the Left 1000 1 false Rotate All Layers &180° Rotate All Layers 180° Rotate All Layers 180° 1000 1 false Scale All &Layers to new Size... Scale All Layers to new Size Scale All Layers to new Size 100000 1 false &Shear All Layers... Shear All Layers Shear All Layers 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false Set Copy F&rom... Set the source for the selected clone layer(s). Set Copy From 1000 1 false diff --git a/krita/pics/svg/light_lazyframeOn.svg b/krita/pics/svg/dark_animation_pause.svg similarity index 54% copy from krita/pics/svg/light_lazyframeOn.svg copy to krita/pics/svg/dark_animation_pause.svg index e4b80f74cd..5836e3802c 100644 --- a/krita/pics/svg/light_lazyframeOn.svg +++ b/krita/pics/svg/dark_animation_pause.svg @@ -1,103 +1,88 @@ + inkscape:current-layer="svg6190" + inkscape:snap-bbox="true" + inkscape:snap-bbox-edge-midpoints="true"> image/svg+xml 2015 Timothée Giet - - - - - - + diff --git a/krita/pics/svg/dark_lazyframeOff.svg b/krita/pics/svg/dark_auto-key-off.svg similarity index 100% rename from krita/pics/svg/dark_lazyframeOff.svg rename to krita/pics/svg/dark_auto-key-off.svg diff --git a/krita/pics/svg/dark_lazyframeOn.svg b/krita/pics/svg/dark_auto-key-on.svg similarity index 100% rename from krita/pics/svg/dark_lazyframeOn.svg rename to krita/pics/svg/dark_auto-key-on.svg diff --git a/krita/pics/svg/light_lazyframeOn.svg b/krita/pics/svg/light_animation_pause.svg similarity index 54% copy from krita/pics/svg/light_lazyframeOn.svg copy to krita/pics/svg/light_animation_pause.svg index e4b80f74cd..55e163fa2b 100644 --- a/krita/pics/svg/light_lazyframeOn.svg +++ b/krita/pics/svg/light_animation_pause.svg @@ -1,103 +1,88 @@ + inkscape:current-layer="svg6190" + inkscape:snap-bbox="true" + inkscape:snap-bbox-edge-midpoints="true"> image/svg+xml - + 2015 Timothée Giet - - - - - - + diff --git a/krita/pics/svg/light_lazyframeOff.svg b/krita/pics/svg/light_auto-key-off.svg similarity index 100% rename from krita/pics/svg/light_lazyframeOff.svg rename to krita/pics/svg/light_auto-key-off.svg diff --git a/krita/pics/svg/light_lazyframeOn.svg b/krita/pics/svg/light_auto-key-on.svg similarity index 100% rename from krita/pics/svg/light_lazyframeOn.svg rename to krita/pics/svg/light_auto-key-on.svg diff --git a/krita/pics/svg/svg-icons.qrc b/krita/pics/svg/svg-icons.qrc index 3efa01283a..ce2c09e817 100644 --- a/krita/pics/svg/svg-icons.qrc +++ b/krita/pics/svg/svg-icons.qrc @@ -1,161 +1,163 @@ broken-preset.svgz dark_addblankframe.svg dark_addcolor.svg dark_addduplicateframe.svg dark_deletekeyframe.svg dark_docker_lock_a.svg dark_docker_lock_b.svg dark_layer-locked.svg dark_layer-unlocked.svg dark_nextframe.svg dark_nextkeyframe.svg dark_lastframe.svg dark_prevkeyframe.svg dark_firstframe.svg dark_pallete_librarysvg.svg dark_passthrough-disabled.svg dark_passthrough-enabled.svg dark_prevframe.svg dark_selection-mode_ants.svg dark_selection-mode_invisible.svg dark_selection-mode_mask.svg dark_transparency-disabled.svg dark_transparency-enabled.svg dark_trim-to-image.svg dark_warning.svg delete.svgz layer-style-disabled.svg layer-style-enabled.svg light_addblankframe.svg light_addcolor.svg light_addduplicateframe.svg light_deletekeyframe.svg light_docker_lock_a.svg light_docker_lock_b.svg light_layer-locked.svg light_layer-unlocked.svg light_nextframe.svg light_pallete_library.svg light_passthrough-disabled.svgz light_passthrough-enabled.svgz light_prevframe.svg light_nextkeyframe.svg light_lastframe.svg light_prevkeyframe.svg light_firstframe.svg light_selection-mode_ants.svg light_selection-mode_invisible.svg light_selection-mode_mask.svg light_timeline_keyframe.svg light_transparency-disabled.svg light_transparency-enabled.svg light_trim-to-image.svg light_warning.svg paintop_presets_disabled.svgz paintop_settings_01.svgz selection-info.svg selection-mode_invisible.svg svg-icons.qrc transparency-locked.svg transparency-unlocked.svg workspace-chooser.svg - light_lazyframeOn.svg - light_lazyframeOff.svg - dark_lazyframeOn.svg - dark_lazyframeOff.svg dark_mirror-view.svg light_mirror-view.svg dark_rotation-reset.svg light_rotation-reset.svg light_smoothing-basic.svg light_smoothing-no.svg light_smoothing-stabilizer.svg light_smoothing-weighted.svg dark_smoothing-basic.svg dark_smoothing-no.svg dark_smoothing-stabilizer.svg dark_smoothing-weighted.svg light_merge-layer-below.svg dark_merge-layer-below.svg light_rotate-canvas-left.svg light_rotate-canvas-right.svg dark_rotate-canvas-left.svg dark_rotate-canvas-right.svg light_gmic.svg dark_gmic.svg light_split-layer.svg dark_split-layer.svg light_color-to-alpha.svg dark_color-to-alpha.svg light_preset-switcher.svg dark_preset-switcher.svg dark_animation_play.svg + dark_animation_pause.svg dark_animation_stop.svg dark_dropframe.svg dark_droppedframes.svg light_animation_play.svg + light_animation_pause.svg light_animation_stop.svg light_dropframe.svg light_droppedframes.svg dark_landscape.svg dark_portrait.svg light_landscape.svg light_portrait.svg dark_interpolation_constant.svg dark_interpolation_linear.svg dark_interpolation_bezier.svg dark_interpolation_sharp.svg dark_interpolation_smooth.svg light_interpolation_bezier.svg light_interpolation_constant.svg light_interpolation_linear.svg light_interpolation_sharp.svg light_interpolation_smooth.svg dark_audio-none.svg dark_audio-volume-high.svg dark_audio-volume-mute.svg + dark_auto-key-on.svg + dark_auto-key-off.svg dark_keyframe-add.svg dark_keyframe-remove.svg dark_zoom-fit.svg dark_zoom-horizontal.svg dark_zoom-vertical.svg light_audio-none.svg light_audio-volume-high.svg light_audio-volume-mute.svg + light_auto-key-on.svg + light_auto-key-off.svg light_keyframe-add.svg light_keyframe-remove.svg light_zoom-fit.svg light_zoom-horizontal.svg light_zoom-vertical.svg dark_showColoring.svg dark_showMarks.svg dark_showColoringOff.svg dark_showMarksOff.svg dark_updateColorize.svg light_showColoring.svg light_showMarks.svg light_showColoringOff.svg light_showMarksOff.svg light_updateColorize.svg light_wheel-light.svg light_wheel-rings.svg light_wheel-sectors.svg dark_wheel-light.svg dark_wheel-rings.svg dark_wheel-sectors.svg dark_infinity.svg light_infinity.svg dark_gamut-mask-on.svg dark_gamut-mask-off.svg light_gamut-mask-off.svg light_gamut-mask-on.svg dark_ratio.svg light_ratio.svg dark_bundle_archive.svg light_bundle_archive.svg dark_python.svg light_python.svg diff --git a/libs/image/kis_convolution_painter.h b/libs/image/kis_convolution_painter.h index 2db38414a7..0911bec934 100644 --- a/libs/image/kis_convolution_painter.h +++ b/libs/image/kis_convolution_painter.h @@ -1,104 +1,109 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CONVOLUTION_PAINTER_H_ #define KIS_CONVOLUTION_PAINTER_H_ #include "kis_types.h" #include "kis_painter.h" #include "kis_image.h" #include "kritaimage_export.h" template class KisConvolutionWorker; enum KisConvolutionBorderOp { BORDER_IGNORE = 0, // read the pixels outside of the application rect BORDER_REPEAT = 1 // Use the border for the missing pixels }; /** * @brief The KisConvolutionPainter class applies a convolution kernel to a paint device. * * * Note: https://bugs.kde.org/show_bug.cgi?id=220310 shows that there's something here * that we need to fix... */ class KRITAIMAGE_EXPORT KisConvolutionPainter : public KisPainter { public: KisConvolutionPainter(); KisConvolutionPainter(KisPaintDeviceSP device); KisConvolutionPainter(KisPaintDeviceSP device, KisSelectionSP selection); enum TestingEnginePreference { NONE, SPATIAL, FFTW }; KisConvolutionPainter(KisPaintDeviceSP device, TestingEnginePreference enginePreference); /** * Convolve all channels in src using the specified kernel; there is only one kernel for all - * channels possible. By default the border pixels are not convolved, that is, convolving - * starts with at (x + kernel.width/2, y + kernel.height/2) and stops at w - (kernel.width/2) - * and h - (kernel.height/2) + * channels possible. + * + * WARNING: The painter will read **more** pixels than you pass in \p areaSize. + * The actual processing area will be: + * QRect(x - kernel.width() / 2, + * y - kernel.height() / 2, + * w + 2 * (kernel.width() / 2), + * h + 2 * (kernel.height() / 2)) * * The border op decides what to do with pixels too close to the edge of the rect as defined above. * * The channels flag determines which set out of color channels, alpha channels. * channels we convolve. * * Note that we do not (currently) support different kernels for * different channels _or_ channel types. * * If you want to convolve a subset of the channels in a pixel, * set those channels with KisPainter::setChannelFlags(); */ void applyMatrix(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, KisConvolutionBorderOp borderOp = BORDER_REPEAT); /** * The caller should ask if the painter needs an explicit transaction iff * the source and destination devices coincide. Otherwise, the transaction is * just not needed. */ bool needsTransaction(const KisConvolutionKernelSP kernel) const; static bool supportsFFTW(); protected: friend class KisConvolutionPainterTest; private: template KisConvolutionWorker* createWorker(const KisConvolutionKernelSP kernel, KisPainter *painter, KoUpdater *progress); bool useFFTImplementation(const KisConvolutionKernelSP kernel) const; private: TestingEnginePreference m_enginePreference; }; #endif //KIS_CONVOLUTION_PAINTER_H_ diff --git a/libs/image/kis_edge_detection_kernel.cpp b/libs/image/kis_edge_detection_kernel.cpp index 2596140852..15ed4fc938 100644 --- a/libs/image/kis_edge_detection_kernel.cpp +++ b/libs/image/kis_edge_detection_kernel.cpp @@ -1,428 +1,414 @@ /* * 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 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_edge_detection_kernel.h" #include "kis_global.h" #include "kis_convolution_kernel.h" #include #include #include #include #include #include KisEdgeDetectionKernel::KisEdgeDetectionKernel() { } /* * This code is very similar to the gaussian kernel code, except unlike the gaussian code, * edge-detection kernels DO use the diagonals. * Except for the simple mode. We implement the simple mode because it is an analog to * the old sobel filter. */ Eigen::Matrix KisEdgeDetectionKernel::createHorizontalMatrix(qreal radius, FilterType type, bool reverse) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, kernelSize); KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; if (type==Prewit) { for (int x = 0; x < kernelSize; x++) { for (int y=0; y KisEdgeDetectionKernel::createVerticalMatrix(qreal radius, FilterType type, bool reverse) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, kernelSize); KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; if (type==Prewit) { for (int y = 0; y < kernelSize; y++) { for (int x=0; x matrix = createHorizontalMatrix(radius, type, reverse); if (denormalize) { return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1); } else { return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } } KisConvolutionKernelSP KisEdgeDetectionKernel::createVerticalKernel(qreal radius, KisEdgeDetectionKernel::FilterType type, bool denormalize, bool reverse) { Eigen::Matrix matrix = createVerticalMatrix(radius, type, reverse); if (denormalize) { return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1); } else { return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } } int KisEdgeDetectionKernel::kernelSizeFromRadius(qreal radius) { return qMax((int)(2 * ceil(sigmaFromRadius(radius)) + 1), 3); } qreal KisEdgeDetectionKernel::sigmaFromRadius(qreal radius) { return 0.3 * radius + 0.3; } void KisEdgeDetectionKernel::applyEdgeDetection(KisPaintDeviceSP device, const QRect &rect, qreal xRadius, qreal yRadius, KisEdgeDetectionKernel::FilterType type, const QBitArray &channelFlags, KoUpdater *progressUpdater, FilterOutput output, bool writeToAlpha) { QPoint srcTopLeft = rect.topLeft(); KisPainter finalPainter(device); finalPainter.setChannelFlags(channelFlags); finalPainter.setProgress(progressUpdater); if (output == pythagorean || output == radian) { KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); x_denormalised->prepareClone(device); y_denormalised->prepareClone(device); KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type); KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type); - qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0; - qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0; - KisConvolutionPainter horizPainterLR(x_denormalised); horizPainterLR.setChannelFlags(channelFlags); horizPainterLR.setProgress(progressUpdater); horizPainterLR.applyMatrix(kernelHorizLeftRight, device, - srcTopLeft - QPoint(0, ceil(horizontalCenter)), - srcTopLeft - QPoint(0, ceil(horizontalCenter)), - rect.size() + QSize(0, 2 * ceil(horizontalCenter)), BORDER_REPEAT); + srcTopLeft, + srcTopLeft, + rect.size(), BORDER_REPEAT); KisConvolutionPainter verticalPainterTB(y_denormalised); verticalPainterTB.setChannelFlags(channelFlags); verticalPainterTB.setProgress(progressUpdater); verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, - srcTopLeft - QPoint(0, ceil(verticalCenter)), - srcTopLeft - QPoint(0, ceil(verticalCenter)), - rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); + srcTopLeft, + srcTopLeft, + rect.size(), BORDER_REPEAT); KisSequentialIterator yItterator(y_denormalised, rect); KisSequentialIterator xItterator(x_denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->channelCount(); const int alphaPos = device->colorSpace()->alphaPos(); KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0); QVector yNormalised(channels); QVector xNormalised(channels); QVector finalNorm(channels); while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised); device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised); device->colorSpace()->normalisedChannelsValue(finalIt.rawData(), finalNorm); if (output == pythagorean) { for (int c = 0; ccolorSpace()); qreal alpha = 0; for (int c = 0; c<(channels-1); c++) { alpha = alpha+finalNorm[c]; } alpha = qMin(alpha/(channels-1), col.opacityF()); col.setOpacity(alpha); memcpy(finalIt.rawData(), col.data(), pixelSize); } else { quint8* f = finalIt.rawData(); finalNorm[alphaPos] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(f, finalNorm); memcpy(finalIt.rawData(), f, pixelSize); } } } else { KisConvolutionKernelSP kernel; - qreal center = 0; bool denormalize = !writeToAlpha; if (output == xGrowth) { kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize); - center = qreal(kernel->width()) / 2.0; } else if (output == xFall) { kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize, true); - center = qreal(kernel->width()) / 2.0; } else if (output == yGrowth) { kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize); - center = qreal(kernel->height()) / 2.0; } else { //yFall kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize, true); - center = qreal(kernel->height()) / 2.0; } if (writeToAlpha) { KisPaintDeviceSP denormalised = new KisPaintDevice(device->colorSpace()); denormalised->prepareClone(device); KisConvolutionPainter kernelP(denormalised); kernelP.setChannelFlags(channelFlags); kernelP.setProgress(progressUpdater); kernelP.applyMatrix(kernel, device, - srcTopLeft - QPoint(0, ceil(center)), - srcTopLeft - QPoint(0, ceil(center)), - rect.size() + QSize(0, 2 * ceil(center)), BORDER_REPEAT); + srcTopLeft, srcTopLeft, + rect.size(), BORDER_REPEAT); KisSequentialIterator iterator(denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->colorChannelCount(); QVector normalised(channels); while (iterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(iterator.rawData(), normalised); KoColor col(finalIt.rawData(), device->colorSpace()); qreal alpha = 0; for (int c = 0; ccolorSpace()->setOpacity(finalIt.rawData(), 1.0, numConseqPixels); } } } } void KisEdgeDetectionKernel::convertToNormalMap(KisPaintDeviceSP device, const QRect &rect, qreal xRadius, qreal yRadius, KisEdgeDetectionKernel::FilterType type, int channelToConvert, QVector channelOrder, QVector channelFlip, const QBitArray &channelFlags, KoUpdater *progressUpdater) { QPoint srcTopLeft = rect.topLeft(); KisPainter finalPainter(device); finalPainter.setChannelFlags(channelFlags); finalPainter.setProgress(progressUpdater); KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); x_denormalised->prepareClone(device); y_denormalised->prepareClone(device); KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(yRadius, type, true, !channelFlip[1]); KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(xRadius, type, true, !channelFlip[0]); - qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0; - qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0; - KisConvolutionPainter horizPainterLR(y_denormalised); horizPainterLR.setChannelFlags(channelFlags); horizPainterLR.setProgress(progressUpdater); horizPainterLR.applyMatrix(kernelHorizLeftRight, device, - srcTopLeft - QPoint(ceil(horizontalCenter), 0), - srcTopLeft - QPoint(ceil(horizontalCenter), 0), - rect.size() + QSize(2 * ceil(horizontalCenter), 0), BORDER_REPEAT); + srcTopLeft, srcTopLeft, + rect.size(), BORDER_REPEAT); KisConvolutionPainter verticalPainterTB(x_denormalised); verticalPainterTB.setChannelFlags(channelFlags); verticalPainterTB.setProgress(progressUpdater); verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, - srcTopLeft - QPoint(0, ceil(verticalCenter)), - srcTopLeft - QPoint(0, ceil(verticalCenter)), - rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); + srcTopLeft, + srcTopLeft, + rect.size(), BORDER_REPEAT); KisSequentialIterator yItterator(y_denormalised, rect); KisSequentialIterator xItterator(x_denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->channelCount(); const int alphaPos = device->colorSpace()->alphaPos(); KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0); QVector yNormalised(channels); QVector xNormalised(channels); QVector finalNorm(channels); while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised); device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised); qreal z = 1.0; if (channelFlip[2]==true){ z=-1.0; } QVector3D normal = QVector3D((xNormalised[channelToConvert]-0.5)*2, (yNormalised[channelToConvert]-0.5)*2, z); normal.normalize(); finalNorm.fill(1.0); for (int c = 0; c<3; c++) { finalNorm[device->colorSpace()->channels().at(channelOrder[c])->displayPosition()] = (normal[channelOrder[c]]/2)+0.5; } finalNorm[alphaPos]= 1.0; quint8* pixel = finalIt.rawData(); device->colorSpace()->fromNormalisedChannelsValue(pixel, finalNorm); memcpy(finalIt.rawData(), pixel, pixelSize); } } diff --git a/libs/image/kis_image_animation_interface.cpp b/libs/image/kis_image_animation_interface.cpp index 2edb94d12e..d2d5e8614c 100644 --- a/libs/image/kis_image_animation_interface.cpp +++ b/libs/image/kis_image_animation_interface.cpp @@ -1,426 +1,428 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_animation_interface.h" #include #include "kis_global.h" #include "kis_image.h" #include "kis_regenerate_frame_stroke_strategy.h" #include "kis_switch_time_stroke_strategy.h" #include "kis_keyframe_channel.h" #include "kis_time_range.h" #include "kis_post_execution_undo_adapter.h" #include "commands_new/kis_switch_current_time_command.h" #include "kis_layer_utils.h" struct KisImageAnimationInterface::Private { Private() : image(0), externalFrameActive(false), frameInvalidationBlocked(false), cachedLastFrameValue(-1), audioChannelMuted(false), audioChannelVolume(0.5), m_currentTime(0), m_currentUITime(0) { } Private(const Private &rhs, KisImage *newImage) : image(newImage), externalFrameActive(false), frameInvalidationBlocked(false), fullClipRange(rhs.fullClipRange), playbackRange(rhs.playbackRange), framerate(rhs.framerate), cachedLastFrameValue(-1), audioChannelFileName(rhs.audioChannelFileName), audioChannelMuted(rhs.audioChannelMuted), audioChannelVolume(rhs.audioChannelVolume), m_currentTime(rhs.m_currentTime), m_currentUITime(rhs.m_currentUITime) { } KisImage *image; bool externalFrameActive; bool frameInvalidationBlocked; KisTimeRange fullClipRange; KisTimeRange playbackRange; int framerate; int cachedLastFrameValue; QString audioChannelFileName; bool audioChannelMuted; qreal audioChannelVolume; KisSwitchTimeStrokeStrategy::SharedTokenWSP switchToken; inline int currentTime() const { return m_currentTime; } inline int currentUITime() const { return m_currentUITime; } inline void setCurrentTime(int value) { m_currentTime = value; } inline void setCurrentUITime(int value) { m_currentUITime = value; } private: int m_currentTime; int m_currentUITime; }; KisImageAnimationInterface::KisImageAnimationInterface(KisImage *image) : QObject(image), m_d(new Private) { m_d->image = image; m_d->framerate = 24; m_d->fullClipRange = KisTimeRange::fromTime(0, 100); connect(this, SIGNAL(sigInternalRequestTimeSwitch(int,bool)), SLOT(switchCurrentTimeAsync(int,bool))); } KisImageAnimationInterface::KisImageAnimationInterface(const KisImageAnimationInterface &rhs, KisImage *newImage) : m_d(new Private(*rhs.m_d, newImage)) { connect(this, SIGNAL(sigInternalRequestTimeSwitch(int,bool)), SLOT(switchCurrentTimeAsync(int,bool))); } KisImageAnimationInterface::~KisImageAnimationInterface() { } bool KisImageAnimationInterface::hasAnimation() const { bool hasAnimation = false; KisLayerUtils::recursiveApplyNodes( m_d->image->root(), [&hasAnimation](KisNodeSP node) { hasAnimation |= node->isAnimated(); }); return hasAnimation; } int KisImageAnimationInterface::currentTime() const { return m_d->currentTime(); } int KisImageAnimationInterface::currentUITime() const { return m_d->currentUITime(); } const KisTimeRange& KisImageAnimationInterface::fullClipRange() const { return m_d->fullClipRange; } void KisImageAnimationInterface::setFullClipRange(const KisTimeRange range) { KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite()); m_d->fullClipRange = range; emit sigFullClipRangeChanged(); } void KisImageAnimationInterface::setFullClipRangeStartTime(int column) { KisTimeRange newRange(column, m_d->fullClipRange.end(), false); setFullClipRange(newRange); } void KisImageAnimationInterface::setFullClipRangeEndTime(int column) { KisTimeRange newRange(m_d->fullClipRange.start(), column, false); setFullClipRange(newRange); } const KisTimeRange& KisImageAnimationInterface::playbackRange() const { return m_d->playbackRange.isValid() ? m_d->playbackRange : m_d->fullClipRange; } void KisImageAnimationInterface::setPlaybackRange(const KisTimeRange range) { KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite()); m_d->playbackRange = range; emit sigPlaybackRangeChanged(); } int KisImageAnimationInterface::framerate() const { return m_d->framerate; } QString KisImageAnimationInterface::audioChannelFileName() const { return m_d->audioChannelFileName; } void KisImageAnimationInterface::setAudioChannelFileName(const QString &fileName) { QFileInfo info(fileName); KIS_SAFE_ASSERT_RECOVER_NOOP(fileName.isEmpty() || info.isAbsolute()); m_d->audioChannelFileName = fileName.isEmpty() ? fileName : info.absoluteFilePath(); emit sigAudioChannelChanged(); } bool KisImageAnimationInterface::isAudioMuted() const { return m_d->audioChannelMuted; } void KisImageAnimationInterface::setAudioMuted(bool value) { m_d->audioChannelMuted = value; emit sigAudioChannelChanged(); } qreal KisImageAnimationInterface::audioVolume() const { return m_d->audioChannelVolume; } void KisImageAnimationInterface::setAudioVolume(qreal value) { m_d->audioChannelVolume = value; emit sigAudioVolumeChanged(); } void KisImageAnimationInterface::setFramerate(int fps) { + KIS_SAFE_ASSERT_RECOVER_RETURN(fps > 0); + m_d->framerate = fps; emit sigFramerateChanged(); } KisImageWSP KisImageAnimationInterface::image() const { return m_d->image; } bool KisImageAnimationInterface::externalFrameActive() const { return m_d->externalFrameActive; } void KisImageAnimationInterface::requestTimeSwitchWithUndo(int time) { if (currentUITime() == time) return; requestTimeSwitchNonGUI(time, true); } void KisImageAnimationInterface::setDefaultProjectionColor(const KoColor &color) { int savedTime = 0; saveAndResetCurrentTime(currentTime(), &savedTime); m_d->image->setDefaultProjectionColor(color); restoreCurrentTime(&savedTime); } void KisImageAnimationInterface::requestTimeSwitchNonGUI(int time, bool useUndo) { emit sigInternalRequestTimeSwitch(time, useUndo); } void KisImageAnimationInterface::explicitlySetCurrentTime(int frameId) { m_d->setCurrentTime(frameId); } void KisImageAnimationInterface::switchCurrentTimeAsync(int frameId, bool useUndo) { if (currentUITime() == frameId) return; const KisTimeRange range = KisTimeRange::calculateIdenticalFramesRecursive(m_d->image->root(), currentUITime()); const bool needsRegeneration = !range.contains(frameId); KisSwitchTimeStrokeStrategy::SharedTokenSP token = m_d->switchToken.toStrongRef(); if (!token || !token->tryResetDestinationTime(frameId, needsRegeneration)) { { KisPostExecutionUndoAdapter *undoAdapter = useUndo ? m_d->image->postExecutionUndoAdapter() : 0; KisSwitchTimeStrokeStrategy *strategy = new KisSwitchTimeStrokeStrategy(frameId, needsRegeneration, this, undoAdapter); m_d->switchToken = strategy->token(); KisStrokeId stroke = m_d->image->startStroke(strategy); m_d->image->endStroke(stroke); } if (needsRegeneration) { KisStrokeStrategy *strategy = new KisRegenerateFrameStrokeStrategy(this); KisStrokeId strokeId = m_d->image->startStroke(strategy); m_d->image->endStroke(strokeId); } } m_d->setCurrentUITime(frameId); emit sigUiTimeChanged(frameId); } void KisImageAnimationInterface::requestFrameRegeneration(int frameId, const KisRegion &dirtyRegion) { KisStrokeStrategy *strategy = new KisRegenerateFrameStrokeStrategy(frameId, dirtyRegion, this); QList jobs = KisRegenerateFrameStrokeStrategy::createJobsData(m_d->image); KisStrokeId stroke = m_d->image->startStroke(strategy); Q_FOREACH (KisStrokeJobData* job, jobs) { m_d->image->addJob(stroke, job); } m_d->image->endStroke(stroke); } void KisImageAnimationInterface::saveAndResetCurrentTime(int frameId, int *savedValue) { m_d->externalFrameActive = true; *savedValue = m_d->currentTime(); m_d->setCurrentTime(frameId); } void KisImageAnimationInterface::restoreCurrentTime(int *savedValue) { m_d->setCurrentTime(*savedValue); m_d->externalFrameActive = false; } void KisImageAnimationInterface::notifyFrameReady() { emit sigFrameReady(m_d->currentTime()); } void KisImageAnimationInterface::notifyFrameCancelled() { emit sigFrameCancelled(); } KisUpdatesFacade* KisImageAnimationInterface::updatesFacade() const { return m_d->image; } void KisImageAnimationInterface::notifyNodeChanged(const KisNode *node, const QRect &rect, bool recursive) { notifyNodeChanged(node, QVector({rect}), recursive); } void KisImageAnimationInterface::notifyNodeChanged(const KisNode *node, const QVector &rects, bool recursive) { if (externalFrameActive() || m_d->frameInvalidationBlocked) return; // even overlay selection masks are not rendered in the cache if (node->inherits("KisSelectionMask")) return; const int currentTime = m_d->currentTime(); KisTimeRange invalidateRange; if (recursive) { invalidateRange = KisTimeRange::calculateAffectedFramesRecursive(node, currentTime); } else { invalidateRange = KisTimeRange::calculateNodeAffectedFrames(node, currentTime); } // we compress the updated rect (atm, no one uses it anyway) QRect unitedRect; Q_FOREACH (const QRect &rc, rects) { unitedRect |= rc; } invalidateFrames(invalidateRange, unitedRect); } void KisImageAnimationInterface::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->cachedLastFrameValue = -1; emit sigFramesChanged(range, rect); } void KisImageAnimationInterface::blockFrameInvalidation(bool value) { m_d->frameInvalidationBlocked = value; } int findLastKeyframeTimeRecursive(KisNodeSP node) { int time = 0; KisKeyframeChannel *channel; Q_FOREACH (channel, node->keyframeChannels()) { KisKeyframeSP keyframe = channel->lastKeyframe(); if (keyframe) { time = std::max(time, keyframe->time()); } } KisNodeSP child = node->firstChild(); while (child) { time = std::max(time, findLastKeyframeTimeRecursive(child)); child = child->nextSibling(); } return time; } int KisImageAnimationInterface::totalLength() { if (m_d->cachedLastFrameValue < 0) { m_d->cachedLastFrameValue = findLastKeyframeTimeRecursive(m_d->image->root()); } int lastKey = m_d->cachedLastFrameValue; lastKey = std::max(lastKey, m_d->fullClipRange.end()); lastKey = std::max(lastKey, m_d->currentUITime()); return lastKey + 1; } diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp index 908659b61d..34593e37ae 100644 --- a/libs/image/kis_image_config.cpp +++ b/libs/image/kis_image_config.cpp @@ -1,641 +1,641 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_config.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include "kis_global.h" #include #include #ifdef Q_OS_MACOS #include #endif KisImageConfig::KisImageConfig(bool readOnly) : m_config(KSharedConfig::openConfig()->group(QString())) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); } #ifdef Q_OS_MACOS // clear /var/folders/ swap path set by old broken Krita swap implementation in order to use new default swap dir. QString swap = m_config.readEntry("swaplocation", ""); if (swap.startsWith("/var/folders/")) { m_config.deleteEntry("swaplocation"); } #endif } KisImageConfig::~KisImageConfig() { if (m_readOnly) return; if (qApp->thread() != QThread::currentThread()) { dbgKrita << "KisImageConfig: requested config synchronization from nonGUI thread! Called from" << kisBacktrace(); return; } m_config.sync(); } bool KisImageConfig::enableProgressReporting(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enableProgressReporting", true) : true; } void KisImageConfig::setEnableProgressReporting(bool value) { m_config.writeEntry("enableProgressReporting", value); } bool KisImageConfig::enablePerfLog(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enablePerfLog", false) :false; } void KisImageConfig::setEnablePerfLog(bool value) { m_config.writeEntry("enablePerfLog", value); } qreal KisImageConfig::transformMaskOffBoundsReadArea() const { return m_config.readEntry("transformMaskOffBoundsReadArea", 0.5); } int KisImageConfig::updatePatchHeight() const { return m_config.readEntry("updatePatchHeight", 512); } void KisImageConfig::setUpdatePatchHeight(int value) { m_config.writeEntry("updatePatchHeight", value); } int KisImageConfig::updatePatchWidth() const { return m_config.readEntry("updatePatchWidth", 512); } void KisImageConfig::setUpdatePatchWidth(int value) { m_config.writeEntry("updatePatchWidth", value); } qreal KisImageConfig::maxCollectAlpha() const { return m_config.readEntry("maxCollectAlpha", 2.5); } qreal KisImageConfig::maxMergeAlpha() const { return m_config.readEntry("maxMergeAlpha", 1.); } qreal KisImageConfig::maxMergeCollectAlpha() const { return m_config.readEntry("maxMergeCollectAlpha", 1.5); } qreal KisImageConfig::schedulerBalancingRatio() const { /** * updates-queue-size / strokes-queue-size */ return m_config.readEntry("schedulerBalancingRatio", 100.); } void KisImageConfig::setSchedulerBalancingRatio(qreal value) { m_config.writeEntry("schedulerBalancingRatio", value); } int KisImageConfig::maxSwapSize(bool requestDefault) const { return !requestDefault ? m_config.readEntry("maxSwapSize", 4096) : 4096; // in MiB } void KisImageConfig::setMaxSwapSize(int value) { m_config.writeEntry("maxSwapSize", value); } int KisImageConfig::swapSlabSize() const { return m_config.readEntry("swapSlabSize", 64); // in MiB } void KisImageConfig::setSwapSlabSize(int value) { m_config.writeEntry("swapSlabSize", value); } int KisImageConfig::swapWindowSize() const { return m_config.readEntry("swapWindowSize", 16); // in MiB } void KisImageConfig::setSwapWindowSize(int value) { m_config.writeEntry("swapWindowSize", value); } int KisImageConfig::tilesHardLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * (1 - pp); } int KisImageConfig::tilesSoftLimit() const { qreal sp = qreal(memorySoftLimitPercent()) / 100.0; return tilesHardLimit() * sp; } int KisImageConfig::poolLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * pp; } qreal KisImageConfig::memoryHardLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryHardLimitPercent", 50.) : 50.; } void KisImageConfig::setMemoryHardLimitPercent(qreal value) { m_config.writeEntry("memoryHardLimitPercent", value); } qreal KisImageConfig::memorySoftLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memorySoftLimitPercent", 2.) : 2.; } void KisImageConfig::setMemorySoftLimitPercent(qreal value) { m_config.writeEntry("memorySoftLimitPercent", value); } qreal KisImageConfig::memoryPoolLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryPoolLimitPercent", 0.0) : 0.0; } void KisImageConfig::setMemoryPoolLimitPercent(qreal value) { m_config.writeEntry("memoryPoolLimitPercent", value); } QString KisImageConfig::safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const { #ifdef Q_OS_MACOS // On OSX, QDir::tempPath() gives us a folder we cannot reply upon (usually // something like /var/folders/.../...) and that will have vanished when we // try to create the tmp file in KisMemoryWindow::KisMemoryWindow using // swapFileTemplate. thus, we just pick the home folder if swapDir does not // tell us otherwise. // the other option here would be to use a "garbled name" temp file (i.e. no name // KRITA_SWAP_FILE_XXXXXX) in an obscure /var/folders place, which is not // nice to the user. having a clearly named swap file in the home folder is // much nicer to Krita's users. // furthermore, this is just a default and swapDir can always be configured // to another location. QString swap = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + '/' + suffix; #else Q_UNUSED(suffix); QString swap = QDir::tempPath(); #endif if (requestDefault) { return swap; } const QString configuredSwap = m_config.readEntry(configKey, swap); if (!configuredSwap.isEmpty()) { swap = configuredSwap; } QString chosenLocation; QStringList proposedSwapLocations; proposedSwapLocations << swap; proposedSwapLocations << QDir::tempPath(); proposedSwapLocations << QDir::homePath(); Q_FOREACH (const QString location, proposedSwapLocations) { if (!QFileInfo(location).isWritable()) continue; /** * On NTFS, isWritable() doesn't check for attributes due to performance * reasons, so we should try it in a brute-force way... * (yes, there is a hacky-global-variable workaround, but let's be safe) */ QTemporaryFile tempFile; tempFile.setFileTemplate(location + '/' + "krita_test_swap_location"); if (tempFile.open() && !tempFile.fileName().isEmpty()) { chosenLocation = location; break; } } if (chosenLocation.isEmpty()) { qCritical() << "CRITICAL: no writable location for a swap file found! Tried the following paths:" << proposedSwapLocations; qCritical() << "CRITICAL: hope I don't crash..."; chosenLocation = swap; } if (chosenLocation != swap) { qWarning() << "WARNING: configured swap location is not writable, using a fall-back location" << swap << "->" << chosenLocation; } return chosenLocation; } QString KisImageConfig::swapDir(bool requestDefault) { return safelyGetWritableTempLocation("swap", "swaplocation", requestDefault); } void KisImageConfig::setSwapDir(const QString &swapDir) { m_config.writeEntry("swaplocation", swapDir); } int KisImageConfig::numberOfOnionSkins() const { return m_config.readEntry("numberOfOnionSkins", 10); } void KisImageConfig::setNumberOfOnionSkins(int value) { m_config.writeEntry("numberOfOnionSkins", value); } int KisImageConfig::onionSkinTintFactor() const { return m_config.readEntry("onionSkinTintFactor", 192); } void KisImageConfig::setOnionSkinTintFactor(int value) { m_config.writeEntry("onionSkinTintFactor", value); } int KisImageConfig::onionSkinOpacity(int offset) const { int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1); if (value < 0) { const int num = numberOfOnionSkins(); const qreal dx = qreal(qAbs(offset)) / num; value = 0.7 * exp(-pow2(dx) / 0.5) * 255; } return value; } void KisImageConfig::setOnionSkinOpacity(int offset, int value) { m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value); } bool KisImageConfig::onionSkinState(int offset) const { bool enableByDefault = (qAbs(offset) <= 2); return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault); } void KisImageConfig::setOnionSkinState(int offset, bool value) { m_config.writeEntry("onionSkinState_" + QString::number(offset), value); } QColor KisImageConfig::onionSkinTintColorBackward() const { return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red)); } void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value) { m_config.writeEntry("onionSkinTintColorBackward", value); } QColor KisImageConfig::onionSkinTintColorForward() const { return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green)); } void KisImageConfig::setOnionSkinTintColorForward(const QColor &value) { m_config.writeEntry("oninSkinTintColorForward", value); } -bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const +bool KisImageConfig::autoKeyEnabled(bool requestDefault) const { return !requestDefault ? m_config.readEntry("lazyFrameCreationEnabled", true) : true; } -void KisImageConfig::setLazyFrameCreationEnabled(bool value) +void KisImageConfig::setAutoKeyEnabled(bool value) { m_config.writeEntry("lazyFrameCreationEnabled", value); } #if defined Q_OS_LINUX #include #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD #include #elif defined Q_OS_WIN #include #elif defined Q_OS_MACOS #include #include #endif int KisImageConfig::totalRAM() { // let's think that default memory size is 1000MiB int totalMemory = 1000; // MiB int error = 1; #if defined Q_OS_LINUX struct sysinfo info; error = sysinfo(&info); if(!error) { totalMemory = info.totalram * info.mem_unit / (1UL << 20); } #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD u_long physmem; # if defined HW_PHYSMEM64 // NetBSD only int mib[] = {CTL_HW, HW_PHYSMEM64}; # else int mib[] = {CTL_HW, HW_PHYSMEM}; # endif size_t len = sizeof(physmem); error = sysctl(mib, 2, &physmem, &len, 0, 0); if(!error) { totalMemory = physmem >> 20; } #elif defined Q_OS_WIN MEMORYSTATUSEX status; status.dwLength = sizeof(status); error = !GlobalMemoryStatusEx(&status); if (!error) { totalMemory = status.ullTotalPhys >> 20; } // For 32 bit windows, the total memory available is at max the 2GB per process memory limit. # if defined ENV32BIT totalMemory = qMin(totalMemory, 2000); # endif #elif defined Q_OS_MACOS int mib[2] = { CTL_HW, HW_MEMSIZE }; u_int namelen = sizeof(mib) / sizeof(mib[0]); uint64_t size; size_t len = sizeof(size); errno = 0; if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) { totalMemory = size >> 20; error = 0; } else { dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno); } #endif if (error) { warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default."; } return totalMemory; } bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const { return !requestDefault ? m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true; } void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value) { m_config.writeEntry("showAdditionalOnionSkinsSettings", value); } int KisImageConfig::defaultFrameColorLabel() const { return m_config.readEntry("defaultFrameColorLabel", 0); } void KisImageConfig::setDefaultFrameColorLabel(int label) { m_config.writeEntry("defaultFrameColorLabel", label); } KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration() { KisProofingConfiguration *proofingConfig= new KisProofingConfiguration(); proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof"); proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA"); proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8"); proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3); if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) { proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } else { proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } QColor def(Qt::green); m_config.readEntry("defaultProofingGamutwarning", def); KoColor col(KoColorSpaceRegistry::instance()->rgb8()); col.fromQColor(def); col.setOpacity(1.0); proofingConfig->warningColor = col; proofingConfig->adaptationState = (double)m_config.readEntry("defaultProofingAdaptationState", 1.0); return toQShared(proofingConfig); } void KisImageConfig::setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState) { m_config.writeEntry("defaultProofingProfileName", proofingSpace->profile()->name()); m_config.writeEntry("defaultProofingProfileModel", proofingSpace->colorModelId().id()); m_config.writeEntry("defaultProofingProfileDepth", proofingSpace->colorDepthId().id()); m_config.writeEntry("defaultProofingProfileIntent", proofingIntent); m_config.writeEntry("defaultProofingBlackpointCompensation", blackPointCompensation); QColor c; c = warningColor.toQColor(); m_config.writeEntry("defaultProofingGamutwarning", c); m_config.writeEntry("defaultProofingAdaptationState",adaptationState); } bool KisImageConfig::useLodForColorizeMask(bool requestDefault) const { return !requestDefault ? m_config.readEntry("useLodForColorizeMask", false) : false; } void KisImageConfig::setUseLodForColorizeMask(bool value) { m_config.writeEntry("useLodForColorizeMask", value); } int KisImageConfig::maxNumberOfThreads(bool defaultValue) const { return (defaultValue ? QThread::idealThreadCount() : m_config.readEntry("maxNumberOfThreads", QThread::idealThreadCount())); } void KisImageConfig::setMaxNumberOfThreads(int value) { if (value == QThread::idealThreadCount()) { m_config.deleteEntry("maxNumberOfThreads"); } else { m_config.writeEntry("maxNumberOfThreads", value); } } int KisImageConfig::frameRenderingClones(bool defaultValue) const { const int defaultClonesCount = qMax(1, maxNumberOfThreads(defaultValue) / 2); return defaultValue ? defaultClonesCount : m_config.readEntry("frameRenderingClones", defaultClonesCount); } void KisImageConfig::setFrameRenderingClones(int value) { m_config.writeEntry("frameRenderingClones", value); } int KisImageConfig::fpsLimit(bool defaultValue) const { int limit = defaultValue ? 100 : m_config.readEntry("fpsLimit", 100); return limit > 0 ? limit : 1; } void KisImageConfig::setFpsLimit(int value) { m_config.writeEntry("fpsLimit", value); } bool KisImageConfig::useOnDiskAnimationCacheSwapping(bool defaultValue) const { return defaultValue ? true : m_config.readEntry("useOnDiskAnimationCacheSwapping", true); } void KisImageConfig::setUseOnDiskAnimationCacheSwapping(bool value) { m_config.writeEntry("useOnDiskAnimationCacheSwapping", value); } QString KisImageConfig::animationCacheDir(bool defaultValue) const { return safelyGetWritableTempLocation("animation_cache", "animationCacheDir", defaultValue); } void KisImageConfig::setAnimationCacheDir(const QString &value) { m_config.writeEntry("animationCacheDir", value); } bool KisImageConfig::useAnimationCacheFrameSizeLimit(bool defaultValue) const { return defaultValue ? true : m_config.readEntry("useAnimationCacheFrameSizeLimit", true); } void KisImageConfig::setUseAnimationCacheFrameSizeLimit(bool value) { m_config.writeEntry("useAnimationCacheFrameSizeLimit", value); } int KisImageConfig::animationCacheFrameSizeLimit(bool defaultValue) const { return defaultValue ? 2500 : m_config.readEntry("animationCacheFrameSizeLimit", 2500); } void KisImageConfig::setAnimationCacheFrameSizeLimit(int value) { m_config.writeEntry("animationCacheFrameSizeLimit", value); } bool KisImageConfig::useAnimationCacheRegionOfInterest(bool defaultValue) const { return defaultValue ? true : m_config.readEntry("useAnimationCacheRegionOfInterest", true); } void KisImageConfig::setUseAnimationCacheRegionOfInterest(bool value) { m_config.writeEntry("useAnimationCacheRegionOfInterest", value); } qreal KisImageConfig::animationCacheRegionOfInterestMargin(bool defaultValue) const { return defaultValue ? 0.25 : m_config.readEntry("animationCacheRegionOfInterestMargin", 0.25); } void KisImageConfig::setAnimationCacheRegionOfInterestMargin(qreal value) { m_config.writeEntry("animationCacheRegionOfInterestMargin", value); } QColor KisImageConfig::selectionOverlayMaskColor(bool defaultValue) const { QColor def(255, 0, 0, 128); return (defaultValue ? def : m_config.readEntry("selectionOverlayMaskColor", def)); } void KisImageConfig::setSelectionOverlayMaskColor(const QColor &color) { m_config.writeEntry("selectionOverlayMaskColor", color); } void KisImageConfig::resetConfig() { KConfigGroup config = KSharedConfig::openConfig()->group(QString()); config.deleteGroup(); } diff --git a/libs/image/kis_image_config.h b/libs/image/kis_image_config.h index 2f25990edc..20bba5a4c4 100644 --- a/libs/image/kis_image_config.h +++ b/libs/image/kis_image_config.h @@ -1,158 +1,158 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_CONFIG_H_ #define KIS_IMAGE_CONFIG_H_ #include #include "kritaimage_export.h" #include "KisProofingConfiguration.h" #include "kis_types.h" class KRITAIMAGE_EXPORT KisImageConfig { public: KisImageConfig(bool readOnly); ~KisImageConfig(); bool enableProgressReporting(bool requestDefault = false) const; void setEnableProgressReporting(bool value); bool enablePerfLog(bool requestDefault = false) const; void setEnablePerfLog(bool value); qreal transformMaskOffBoundsReadArea() const; int updatePatchHeight() const; void setUpdatePatchHeight(int value); int updatePatchWidth() const; void setUpdatePatchWidth(int value); qreal maxCollectAlpha() const; qreal maxMergeAlpha() const; qreal maxMergeCollectAlpha() const; qreal schedulerBalancingRatio() const; void setSchedulerBalancingRatio(qreal value); int maxSwapSize(bool requestDefault = false) const; void setMaxSwapSize(int value); int swapSlabSize() const; void setSwapSlabSize(int value); int swapWindowSize() const; void setSwapWindowSize(int value); int tilesHardLimit() const; // MiB int tilesSoftLimit() const; // MiB int poolLimit() const; // MiB qreal memoryHardLimitPercent(bool requestDefault = false) const; // % of total RAM qreal memorySoftLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() * (1 - 0.01 * memoryPoolLimitPercent()) qreal memoryPoolLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() void setMemoryHardLimitPercent(qreal value); void setMemorySoftLimitPercent(qreal value); void setMemoryPoolLimitPercent(qreal value); static int totalRAM(); // MiB /** * @return a specific directory for the swapfile, if set. If not set, return an * empty QString and use the default KDE directory. */ QString swapDir(bool requestDefault = false); void setSwapDir(const QString &swapDir); int numberOfOnionSkins() const; void setNumberOfOnionSkins(int value); int onionSkinTintFactor() const; void setOnionSkinTintFactor(int value); int onionSkinOpacity(int offset) const; void setOnionSkinOpacity(int offset, int value); bool onionSkinState(int offset) const; void setOnionSkinState(int offset, bool value); QColor onionSkinTintColorBackward() const; void setOnionSkinTintColorBackward(const QColor &value); QColor onionSkinTintColorForward() const; void setOnionSkinTintColorForward(const QColor &value); - bool lazyFrameCreationEnabled(bool requestDefault = false) const; - void setLazyFrameCreationEnabled(bool value); + bool autoKeyEnabled(bool requestDefault = false) const; + void setAutoKeyEnabled(bool value); bool showAdditionalOnionSkinsSettings(bool requestDefault = false) const; void setShowAdditionalOnionSkinsSettings(bool value); int defaultFrameColorLabel() const; void setDefaultFrameColorLabel(int label); KisProofingConfigurationSP defaultProofingconfiguration(); void setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState); bool useLodForColorizeMask(bool requestDefault = false) const; void setUseLodForColorizeMask(bool value); int maxNumberOfThreads(bool defaultValue = false) const; void setMaxNumberOfThreads(int value); int frameRenderingClones(bool defaultValue = false) const; void setFrameRenderingClones(int value); int fpsLimit(bool defaultValue = false) const; void setFpsLimit(int value); bool useOnDiskAnimationCacheSwapping(bool defaultValue = false) const; void setUseOnDiskAnimationCacheSwapping(bool value); QString animationCacheDir(bool defaultValue = false) const; void setAnimationCacheDir(const QString &value); bool useAnimationCacheFrameSizeLimit(bool defaultValue = false) const; void setUseAnimationCacheFrameSizeLimit(bool value); int animationCacheFrameSizeLimit(bool defaultValue = false) const; void setAnimationCacheFrameSizeLimit(int value); bool useAnimationCacheRegionOfInterest(bool defaultValue = false) const; void setUseAnimationCacheRegionOfInterest(bool value); qreal animationCacheRegionOfInterestMargin(bool defaultValue = false) const; void setAnimationCacheRegionOfInterestMargin(qreal value); QColor selectionOverlayMaskColor(bool defaultValue = false) const; void setSelectionOverlayMaskColor(const QColor &color); static void resetConfig(); private: Q_DISABLE_COPY(KisImageConfig) QString safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const; private: KConfigGroup m_config; bool m_readOnly; }; #endif /* KIS_IMAGE_CONFIG_H_ */ diff --git a/libs/image/kis_transaction_data.cpp b/libs/image/kis_transaction_data.cpp index 0e0db683df..81843bda27 100644 --- a/libs/image/kis_transaction_data.cpp +++ b/libs/image/kis_transaction_data.cpp @@ -1,343 +1,343 @@ /* * 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_transaction_data.h" #include "kis_pixel_selection.h" #include "kis_paint_device.h" #include "kis_paint_device_frames_interface.h" #include "kis_datamanager.h" #include "kis_image.h" #include "KoColor.h" //#define DEBUG_TRANSACTIONS #ifdef DEBUG_TRANSACTIONS # define DEBUG_ACTION(action) dbgKrita << action << "for" << m_d->device->dataManager() #else # define DEBUG_ACTION(action) #endif class Q_DECL_HIDDEN KisTransactionData::Private { public: KisPaintDeviceSP device; KisMementoSP memento; bool firstRedo; bool transactionFinished; QPoint oldOffset; QPoint newOffset; KoColor oldDefaultPixel; bool defaultPixelChanged = false; bool savedOutlineCacheValid; QPainterPath savedOutlineCache; QScopedPointer flattenUndoCommand; bool resetSelectionOutlineCache; int transactionTime; int transactionFrameId; KisDataManagerSP savedDataManager; KUndo2Command newFrameCommand; void possiblySwitchCurrentTime(); KisDataManagerSP dataManager(); void moveDevice(const QPoint newOffset); void tryCreateNewFrame(KisPaintDeviceSP device, int time); }; KisTransactionData::KisTransactionData(const KUndo2MagicString& name, KisPaintDeviceSP device, bool resetSelectionOutlineCache, KUndo2Command* parent) : KUndo2Command(name, parent) , m_d(new Private()) { m_d->resetSelectionOutlineCache = resetSelectionOutlineCache; setTimedID(-1); possiblyFlattenSelection(device); init(device); saveSelectionOutlineCache(); } #include "kis_raster_keyframe_channel.h" #include "kis_image_config.h" void KisTransactionData::Private::tryCreateNewFrame(KisPaintDeviceSP device, int time) { if (!device->framesInterface()) return; KisImageConfig cfg(true); - if (!cfg.lazyFrameCreationEnabled()) return; + if (!cfg.autoKeyEnabled()) return; KisRasterKeyframeChannel *channel = device->keyframeChannel(); KIS_ASSERT_RECOVER(channel) { return; } KisKeyframeSP keyframe = channel->keyframeAt(time); if (!keyframe) { keyframe = channel->activeKeyframeAt(time); KisKeyframeSP newKeyframe = channel->copyKeyframe(keyframe, time, &newFrameCommand); newKeyframe->setColorLabel(KisImageConfig(true).defaultFrameColorLabel()); } } void KisTransactionData::init(KisPaintDeviceSP device) { m_d->device = device; DEBUG_ACTION("Transaction started"); m_d->oldOffset = QPoint(device->x(), device->y()); m_d->oldDefaultPixel = device->defaultPixel(); m_d->firstRedo = true; m_d->transactionFinished = false; m_d->transactionTime = device->defaultBounds()->currentTime(); m_d->tryCreateNewFrame(m_d->device, m_d->transactionTime); m_d->transactionFrameId = device->framesInterface() ? device->framesInterface()->currentFrameId() : -1; m_d->savedDataManager = m_d->transactionFrameId >= 0 ? m_d->device->framesInterface()->frameDataManager(m_d->transactionFrameId) : m_d->device->dataManager(); m_d->memento = m_d->savedDataManager->getMemento(); } KisTransactionData::~KisTransactionData() { Q_ASSERT(m_d->memento); m_d->savedDataManager->purgeHistory(m_d->memento); delete m_d; } void KisTransactionData::Private::moveDevice(const QPoint newOffset) { if (transactionFrameId >= 0) { device->framesInterface()->setFrameOffset(transactionFrameId, newOffset); } else { device->moveTo(newOffset); } } void KisTransactionData::endTransaction() { if(!m_d->transactionFinished) { // make sure the time didn't change during the transaction KIS_ASSERT_RECOVER_RETURN( m_d->transactionTime == m_d->device->defaultBounds()->currentTime()); DEBUG_ACTION("Transaction ended"); m_d->transactionFinished = true; m_d->savedDataManager->commit(); m_d->newOffset = QPoint(m_d->device->x(), m_d->device->y()); m_d->defaultPixelChanged = m_d->oldDefaultPixel != m_d->device->defaultPixel(); } } void KisTransactionData::startUpdates() { if (m_d->transactionFrameId == -1 || m_d->transactionFrameId == m_d->device->framesInterface()->currentFrameId()) { QRect rc; QRect mementoExtent = m_d->memento->extent(); if (m_d->newOffset == m_d->oldOffset) { rc = mementoExtent.translated(m_d->device->x(), m_d->device->y()); } else { QRect totalExtent = m_d->savedDataManager->extent() | mementoExtent; rc = totalExtent.translated(m_d->oldOffset) | totalExtent.translated(m_d->newOffset); } if (m_d->defaultPixelChanged) { rc |= m_d->device->defaultBounds()->bounds(); } m_d->device->setDirty(rc); } else { m_d->device->framesInterface()->invalidateFrameCache(m_d->transactionFrameId); } } void KisTransactionData::possiblyNotifySelectionChanged() { KisPixelSelectionSP pixelSelection = dynamic_cast(m_d->device.data()); KisSelectionSP selection; if (pixelSelection && (selection = pixelSelection->parentSelection())) { selection->notifySelectionChanged(); } } void KisTransactionData::possiblyResetOutlineCache() { KisPixelSelectionSP pixelSelection; if (m_d->resetSelectionOutlineCache && (pixelSelection = dynamic_cast(m_d->device.data()))) { pixelSelection->invalidateOutlineCache(); } } void KisTransactionData::possiblyFlattenSelection(KisPaintDeviceSP device) { KisPixelSelectionSP pixelSelection = dynamic_cast(device.data()); if (pixelSelection) { KisSelection *selection = pixelSelection->parentSelection().data(); if (selection) { m_d->flattenUndoCommand.reset(selection->flatten()); if (m_d->flattenUndoCommand) { m_d->flattenUndoCommand->redo(); } } } } void KisTransactionData::doFlattenUndoRedo(bool undo) { KisPixelSelectionSP pixelSelection = dynamic_cast(m_d->device.data()); if (pixelSelection) { if (m_d->flattenUndoCommand) { if (undo) { m_d->flattenUndoCommand->undo(); } else { m_d->flattenUndoCommand->redo(); } } } } void KisTransactionData::Private::possiblySwitchCurrentTime() { if (device->defaultBounds()->currentTime() == transactionTime) return; qWarning() << "WARNING: undo command has been executed, when another frame has been active. That shouldn't have happened."; device->requestTimeSwitch(transactionTime); } void KisTransactionData::redo() { //KUndo2QStack calls redo(), so the first call needs to be blocked if (m_d->firstRedo) { m_d->firstRedo = false; possiblyResetOutlineCache(); possiblyNotifySelectionChanged(); return; } doFlattenUndoRedo(false); restoreSelectionOutlineCache(false); m_d->newFrameCommand.redo(); DEBUG_ACTION("Redo()"); Q_ASSERT(m_d->memento); m_d->savedDataManager->rollforward(m_d->memento); if (m_d->newOffset != m_d->oldOffset) { m_d->moveDevice(m_d->newOffset); } m_d->possiblySwitchCurrentTime(); startUpdates(); possiblyNotifySelectionChanged(); } void KisTransactionData::undo() { DEBUG_ACTION("Undo()"); Q_ASSERT(m_d->memento); m_d->savedDataManager->rollback(m_d->memento); if (m_d->newOffset != m_d->oldOffset) { m_d->moveDevice(m_d->oldOffset); } restoreSelectionOutlineCache(true); doFlattenUndoRedo(true); m_d->possiblySwitchCurrentTime(); startUpdates(); possiblyNotifySelectionChanged(); m_d->newFrameCommand.undo(); } void KisTransactionData::saveSelectionOutlineCache() { m_d->savedOutlineCacheValid = false; KisPixelSelectionSP pixelSelection = dynamic_cast(m_d->device.data()); if (pixelSelection) { m_d->savedOutlineCacheValid = pixelSelection->outlineCacheValid(); if (m_d->savedOutlineCacheValid) { m_d->savedOutlineCache = pixelSelection->outlineCache(); possiblyResetOutlineCache(); } } } void KisTransactionData::restoreSelectionOutlineCache(bool undo) { KisPixelSelectionSP pixelSelection = dynamic_cast(m_d->device.data()); if (pixelSelection) { bool savedOutlineCacheValid; QPainterPath savedOutlineCache; savedOutlineCacheValid = pixelSelection->outlineCacheValid(); if (savedOutlineCacheValid) { savedOutlineCache = pixelSelection->outlineCache(); } if (m_d->savedOutlineCacheValid) { pixelSelection->setOutlineCache(m_d->savedOutlineCache); } else { pixelSelection->invalidateOutlineCache(); } m_d->savedOutlineCacheValid = savedOutlineCacheValid; if (m_d->savedOutlineCacheValid) { m_d->savedOutlineCache = savedOutlineCache; } } } diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index c78ee941d6..20434b6899 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,656 +1,658 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) if (ANDROID) add_definitions(-DQT_OPENGL_ES_3) add_definitions(-DHAS_ONLY_OPENGL_ES) include_directories (${Qt5AndroidExtras_INCLUDE_DIRS}) endif() add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_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 canvas/KisSnapPixelStrategy.cpp canvas/KisMirrorAxisConfig.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_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp dialogs/KisDlgChangeCloneSource.cpp dialogs/KisRecoverNamedAutosaveDialog.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 KisPaintopPropertiesBase.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 KisOcioConfiguration.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc 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 KisNodeDisplayModeAdapter.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 KisDecorationsManager.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 KisResourceServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp KisChangeCloneLayersCommand.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp opengl/KisOpenGLModeProber.cpp opengl/KisScreenInformationAdapter.cpp kis_fps_decoration.cpp tool/KisToolChangesTracker.cpp tool/KisToolChangesTrackerData.cpp 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/KisAsyncronousStrokeUpdateHelper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp tool/strokes/KisNodeSelectionRecipe.cpp tool/KisSelectionToolFactoryBase.cpp tool/KisToolPaintFactoryBase.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_transport_controls.cpp + widgets/kis_utility_title_bar.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_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_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/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.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_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KisLayerStyleAngleSelector.cpp widgets/KisMemoryReportButton.cpp widgets/KisDitherWidget.cpp KisPaletteEditor.cpp dialogs/KisDlgPaletteEditor.cpp widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp utils/KisSpinBoxSplineUnitConverter.cpp utils/KisClipboardUtil.cpp utils/KisDitherUtil.cpp utils/KisFileIconCreator.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp input/kis_zoom_and_rotate_action.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactories.cpp actions/KisTransformToolActivationCommand.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.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 canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp KisCanvasWindow.cpp KisImportExportErrorCode.cpp KisImportExportAdditionalChecks.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisRssReader.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h KisMouseClickEater.cpp KisDecorationsWrapperLayer.cpp KoDocumentInfoDlg.cpp KoDocumentInfo.cpp ) if(WIN32) # Private headers are needed for: # * KisDlgCustomTabletResolution # * KisScreenInformationAdapter include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_win.cpp ) if (NOT USE_QT_TABLET_WINDOWS) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp input/wintab/kis_tablet_support_win8.cpp ) else() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} dialogs/KisDlgCustomTabletResolution.cpp ) endif() endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_unix.cpp ) endif() if (ENABLE_UPDATERS) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} utils/KisAppimageUpdater.cpp ) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} utils/KisUpdaterBase.cpp utils/KisManualUpdater.cpp utils/KisUpdaterStatus.cpp ) endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_extended_modifiers_mapper_osx.mm osx.mm ) endif() if (ANDROID) set (kritaui_LIB_SRCS ${kritaui_LIB_SRCS} KisAndroidFileManager.cpp) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui widgets/KisDitherWidget.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/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui forms/WdgDlgPaletteEditor.ui forms/KisNewsPage.ui forms/wdgGamutMaskToolbar.ui forms/wdgchangeclonesource.ui forms/koDocumentInfoAboutWidget.ui forms/koDocumentInfoAuthorWidget.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui dialogs/KisRecoverNamedAutosaveDialog.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 layerstyles/wdgKisLayerStyleAngleSelector.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) if(WIN32) if(USE_QT_TABLET_WINDOWS) ki18n_wrap_ui(kritaui_LIB_SRCS dialogs/KisDlgCustomTabletResolution.ui ) else() ki18n_wrap_ui(kritaui_LIB_SRCS input/wintab/kis_screen_size_choice_dialog.ui ) endif() endif() 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 kritaversion kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils kritaresources ${PNG_LIBRARIES} LibExiv2::LibExiv2 ) if (ANDROID) target_link_libraries(kritaui GLESv3) target_link_libraries(kritaui Qt5::Gui) target_link_libraries(kritaui Qt5::AndroidExtras) endif() if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE AND NOT ANDROID) 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 () if (UNIX AND BUILD_TESTING AND ENABLE_UPDATERS) install(FILES tests/data/AppImageUpdateDummy PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) endif () diff --git a/libs/ui/canvas/kis_animation_player.cpp b/libs/ui/canvas/kis_animation_player.cpp index e7f70ef19b..5358df9ccf 100644 --- a/libs/ui/canvas/kis_animation_player.cpp +++ b/libs/ui/canvas/kis_animation_player.cpp @@ -1,624 +1,661 @@ /* * 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_algebra_2d.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 "KisSyncedAudioPlayback.h" #include "kis_signal_compressor_with_param.h" #include "kis_image_config.h" #include #include "KisViewManager.h" #include "kis_icon_utils.h" #include "KisPart.h" #include "dialogs/KisAsyncAnimationCacheRenderDialog.h" #include "KisRollingMeanAccumulatorWrapper.h" - struct KisAnimationPlayer::Private { public: Private(KisAnimationPlayer *_q) : q(_q), realFpsAccumulator(24), droppedFpsAccumulator(24), droppedFramesPortion(24), dropFramesMode(true), nextFrameExpectedTime(0), expectedInterval(0), currentFrame(0), lastTimerInterval(0), lastPaintedFrame(0), playbackStatisticsCompressor(1000, KisSignalCompressor::FIRST_INACTIVE), stopAudioOnScrubbingCompressor(100, KisSignalCompressor::POSTPONE), audioOffsetTolerance(-1) {} KisAnimationPlayer *q; - bool useFastFrameUpload; - bool playing; - + KisAnimationPlayer::PlaybackState playbackState; QTimer *timer; /// The frame user started playback from - int uiFrame; + int playbackOriginFrame; int firstFrame; int lastFrame; qreal playbackSpeed; KisCanvas2 *canvas; KisSignalAutoConnectionsStore cancelStrokeConnections; QElapsedTimer realFpsTimer; KisRollingMeanAccumulatorWrapper realFpsAccumulator; KisRollingMeanAccumulatorWrapper droppedFpsAccumulator; KisRollingMeanAccumulatorWrapper droppedFramesPortion; - bool dropFramesMode; + bool useFastFrameUpload; + /// Measures time since playback (re)started QElapsedTimer playbackTime; int nextFrameExpectedTime; int expectedInterval; /// The frame the current playback (re)started on int initialFrame; /// The frame currently displayed int currentFrame; int lastTimerInterval; int lastPaintedFrame; KisSignalCompressor playbackStatisticsCompressor; QScopedPointer syncedAudio; QScopedPointer > audioSyncScrubbingCompressor; KisSignalCompressor stopAudioOnScrubbingCompressor; int audioOffsetTolerance; - void stopImpl(bool doUpdates); + void stopImpl(); int incFrame(int frame, int inc) { frame += inc; if (frame > lastFrame) { const int framesFromFirst = frame - firstFrame; const int rangeLength = lastFrame - firstFrame + 1; frame = firstFrame + framesFromFirst % rangeLength; } return frame; } qint64 framesToMSec(qreal value, int fps) const { return qRound(value / fps * 1000.0); } qreal msecToFrames(qint64 value, int fps) const { return qreal(value) * fps / 1000.0; } int framesToWalltime(qreal frame, int fps) const { return qRound(framesToMSec(frame, fps) / playbackSpeed); } qreal walltimeToFrames(qint64 time, int fps) const { return msecToFrames(time, fps) * playbackSpeed; } qreal playbackTimeInFrames(int fps) const { const qint64 cycleLength = lastFrame - firstFrame + 1; const qreal framesPlayed = walltimeToFrames(playbackTime.elapsed(), fps); const qreal framesSinceFirst = std::fmod(initialFrame + framesPlayed - firstFrame, cycleLength); return firstFrame + framesSinceFirst; } }; KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas) : QObject(canvas) , m_d(new Private(this)) { m_d->useFastFrameUpload = false; - m_d->playing = false; + m_d->playbackState = STOPPED; 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(true); 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(QString,QString)), SLOT(slotOnAudioError(QString,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("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(true); 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); } - if (m_d->playing) { + if (isPlaying()) { slotUpdatePlaybackTimer(); } } void KisAnimationPlayer::slotUpdatePlaybackTimer() { m_d->timer->stop(); const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); const KisTimeRange &playBackRange = animation->playbackRange(); if (!playBackRange.isValid()) return; const int fps = animation->framerate(); m_d->initialFrame = isPlaying() ? m_d->currentFrame : animation->currentUITime(); m_d->firstFrame = playBackRange.start(); m_d->lastFrame = playBackRange.end(); m_d->currentFrame = qBound(m_d->firstFrame, m_d->currentFrame, m_d->lastFrame); - m_d->expectedInterval = m_d->framesToWalltime(1, fps); m_d->lastTimerInterval = m_d->expectedInterval; if (m_d->syncedAudio) { m_d->syncedAudio->setSpeed(m_d->playbackSpeed); const qint64 expectedAudioTime = m_d->framesToMSec(m_d->currentFrame, fps); if (qAbs(m_d->syncedAudio->position() - expectedAudioTime) > m_d->framesToMSec(1.5, fps)) { m_d->syncedAudio->syncWithVideo(expectedAudioTime); } } 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()) { KisImageConfig cfg(true); const int dimensionLimit = cfg.useAnimationCacheFrameSizeLimit() ? cfg.animationCacheFrameSizeLimit() : std::numeric_limits::max(); const int maxImageDimension = KisAlgebra2D::maxDimension(m_d->canvas->image()->bounds()); const QRect regionOfInterest = cfg.useAnimationCacheRegionOfInterest() && maxImageDimension > dimensionLimit ? m_d->canvas->regionOfInterest() : m_d->canvas->coordinatesConverter()->imageRectInImagePixels(); const QRect minimalNeedRect = m_d->canvas->coordinatesConverter()->widgetRectInImagePixels().toAlignedRect() & m_d->canvas->coordinatesConverter()->imageRectInImagePixels(); m_d->canvas->frameCache()->dropLowQualityFrames(range, regionOfInterest, minimalNeedRect); KisAsyncAnimationCacheRenderDialog dlg(m_d->canvas->frameCache(), range, 200); dlg.setRegionOfInterest(regionOfInterest); KisAsyncAnimationCacheRenderDialog::Result result = dlg.regenerateRange(m_d->canvas->viewManager()); if (result != KisAsyncAnimationCacheRenderDialog::RenderComplete) { return; } m_d->canvas->setRenderingLimit(regionOfInterest); } } - m_d->playing = true; + if (m_d->playbackState == STOPPED) { + m_d->playbackOriginFrame = animation->currentUITime(); + m_d->currentFrame = m_d->playbackOriginFrame; + } + + m_d->playbackState = PLAYING; - m_d->uiFrame = animation->currentUITime(); - m_d->currentFrame = m_d->uiFrame; slotUpdatePlaybackTimer(); m_d->lastPaintedFrame = -1; connectCancelSignals(); if (m_d->syncedAudio) { KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface(); m_d->syncedAudio->play(m_d->framesToMSec(m_d->currentFrame, animationInterface->framerate())); } + emit sigPlaybackStateChanged(isPlaying()); emit sigPlaybackStarted(); } +void KisAnimationPlayer::pause() +{ + m_d->stopImpl(); + + m_d->playbackState = PAUSED; + + KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface(); + if(animationInterface) { + animationInterface->switchCurrentTimeAsync(m_d->currentFrame); + } + + emit sigPlaybackStateChanged(isPlaying()); + emit sigPlaybackStopped(); +} + +void KisAnimationPlayer::stop() +{ + m_d->stopImpl(); + + m_d->playbackState = STOPPED; + + emit sigPlaybackStateChanged(isPlaying()); + emit sigPlaybackStopped(); +} + +void KisAnimationPlayer::goToPlaybackOrigin() +{ + KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); + if (animation->currentUITime() == m_d->playbackOriginFrame) { + m_d->canvas->refetchDataFromImage(); + } else { + animation->switchCurrentTimeAsync(m_d->playbackOriginFrame); + } +} + +void KisAnimationPlayer::goToStartFrame() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->canvas); + + KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); + const int startFrame = animation->playbackRange().start(); + + animation->switchCurrentTimeAsync(startFrame); +} + +void KisAnimationPlayer::forcedStopOnExit() +{ + m_d->stopImpl(); +} -void KisAnimationPlayer::Private::stopImpl(bool doUpdates) +void KisAnimationPlayer::Private::stopImpl() { if (syncedAudio) { syncedAudio->stop(); } q->disconnectCancelSignals(); - timer->stop(); - playing = false; canvas->setRenderingLimit(QRect()); - - if (doUpdates) { - KisImageAnimationInterface *animation = canvas->image()->animationInterface(); - if (animation->currentUITime() == uiFrame) { - canvas->refetchDataFromImage(); - } else { - animation->switchCurrentTimeAsync(uiFrame); - } - } - - emit q->sigPlaybackStopped(); } -void KisAnimationPlayer::stop() +bool KisAnimationPlayer::isPlaying() { - m_d->stopImpl(true); + return m_d->playbackState == PLAYING; } -void KisAnimationPlayer::forcedStopOnExit() +bool KisAnimationPlayer::isPaused() { - m_d->stopImpl(false); + return m_d->playbackState == PAUSED; } -bool KisAnimationPlayer::isPlaying() +bool KisAnimationPlayer::isStopped() { - return m_d->playing; + return m_d->playbackState == STOPPED; } -int KisAnimationPlayer::currentTime() +int KisAnimationPlayer::visibleFrame() { return m_d->lastPaintedFrame; } void KisAnimationPlayer::displayFrame(int time) { uploadFrame(time, true); } void KisAnimationPlayer::slotUpdate() { uploadFrame(-1, false); } void KisAnimationPlayer::uploadFrame(int frame, bool forceSyncAudio) { KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface(); const int fps = animationInterface->framerate(); const bool syncToAudio = !forceSyncAudio && m_d->dropFramesMode && m_d->syncedAudio && m_d->syncedAudio->isPlaying(); if (frame < 0) { if (m_d->dropFramesMode) { const qreal currentTimeInFrames = syncToAudio ? m_d->msecToFrames(m_d->syncedAudio->position(), fps) : m_d->playbackTimeInFrames(fps); frame = qFloor(currentTimeInFrames); const int timeToNextFrame = m_d->framesToWalltime(frame + 1 - currentTimeInFrames, fps); m_d->lastTimerInterval = qMax(0, timeToNextFrame); if (frame < m_d->currentFrame) { // Returned to beginning of animation. Restart audio playback. forceSyncAudio = true; } } else { const qint64 currentTime = m_d->playbackTime.elapsed(); const qint64 framesDiff = currentTime - m_d->nextFrameExpectedTime; frame = m_d->incFrame(m_d->currentFrame, 1); m_d->nextFrameExpectedTime = currentTime + m_d->expectedInterval; m_d->lastTimerInterval = qMax(0.0, m_d->lastTimerInterval - 0.5 * framesDiff); } m_d->currentFrame = frame; m_d->timer->start(m_d->lastTimerInterval); m_d->playbackStatisticsCompressor.start(); } if (m_d->syncedAudio && (!syncToAudio || forceSyncAudio)) { const int msecTime = m_d->framesToMSec(frame, fps); if (isPlaying()) { slotSyncScrubbingAudio(msecTime); } else { m_d->audioSyncScrubbingCompressor->start(msecTime); } } bool useFallbackUploadMethod = !m_d->canvas->frameCache(); if (m_d->canvas->frameCache() && m_d->canvas->frameCache()->shouldUploadNewFrame(frame, m_d->lastPaintedFrame)) { if (m_d->canvas->frameCache()->uploadFrame(frame)) { m_d->canvas->updateCanvas(); m_d->useFastFrameUpload = true; } else { useFallbackUploadMethod = true; } } if (useFallbackUploadMethod && m_d->canvas->image()->animationInterface()->hasAnimation()) { m_d->useFastFrameUpload = false; if (m_d->canvas->image()->tryBarrierLock(true)) { m_d->canvas->image()->unlock(); // no OpenGL cache or the frame just not cached yet animationInterface->switchCurrentTimeAsync(frame); } } if (!m_d->realFpsTimer.isValid()) { m_d->realFpsTimer.start(); } else { const int elapsed = m_d->realFpsTimer.restart(); m_d->realFpsAccumulator(elapsed); if (m_d->lastPaintedFrame >= 0) { 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 / m_d->realFpsAccumulator.rollingMean() << "DFPS:" << 1000.0 / m_d->droppedFpsAccumulator.rollingMean() << ppVar(numFrames); #endif /* PLAYER_DEBUG_FRAMERATE */ } } m_d->lastPaintedFrame = frame; emit sigFrameChanged(); } qreal KisAnimationPlayer::effectiveFps() const { return 1000.0 / m_d->droppedFpsAccumulator.rollingMean(); } qreal KisAnimationPlayer::realFps() const { return 1000.0 / m_d->realFpsAccumulator.rollingMean(); } qreal KisAnimationPlayer::framesDroppedPortion() const { return m_d->droppedFramesPortion.rollingMean(); } 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) { + if (isPlaying()) { slotUpdatePlaybackTimer(); } } diff --git a/libs/ui/canvas/kis_animation_player.h b/libs/ui/canvas/kis_animation_player.h index 28996485a9..9f9144ebb9 100644 --- a/libs/ui/canvas/kis_animation_player.h +++ b/libs/ui/canvas/kis_animation_player.h @@ -1,90 +1,101 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ANIMATION_PLAYER_H #define KIS_ANIMATION_PLAYER_H #include #include #include "kritaui_export.h" class KisCanvas2; class KRITAUI_EXPORT KisAnimationPlayer : public QObject { Q_OBJECT public: KisAnimationPlayer(KisCanvas2 *canvas); ~KisAnimationPlayer() override; void play(); + void pause(); void stop(); + void goToPlaybackOrigin(); + void goToStartFrame(); void displayFrame(int time); bool isPlaying(); - int currentTime(); + bool isPaused(); + bool isStopped(); + int visibleFrame(); qreal playbackSpeed(); void forcedStopOnExit(); qreal effectiveFps() const; qreal realFps() const; qreal framesDroppedPortion() const; Q_SIGNALS: void sigFrameChanged(); void sigPlaybackStarted(); void sigPlaybackStopped(); + void sigPlaybackStateChanged(bool value); void sigPlaybackStatisticsUpdated(); void sigFullClipRangeChanged(); public Q_SLOTS: void slotUpdate(); void slotCancelPlayback(); void slotCancelPlaybackSafe(); void slotUpdatePlaybackSpeed(double value); void slotUpdatePlaybackTimer(); void slotUpdateDropFramesMode(); private Q_SLOTS: void slotSyncScrubbingAudio(int msecTime); void slotTryStopScrubbingAudio(); void slotUpdateAudioChunkLength(); void slotAudioChannelChanged(); void slotAudioVolumeChanged(); void slotOnAudioError(const QString &fileName, const QString &message); - - - private: void connectCancelSignals(); void disconnectCancelSignals(); void uploadFrame(int time, bool forceSyncAudio); + void refreshCanvas(); + private: struct Private; QScopedPointer m_d; + + enum PlaybackState { + STOPPED, + PAUSED, + PLAYING + }; }; #endif diff --git a/libs/ui/dialogs/KisSessionManagerDialog.cpp b/libs/ui/dialogs/KisSessionManagerDialog.cpp index 75ddb0064a..5303fa9746 100644 --- a/libs/ui/dialogs/KisSessionManagerDialog.cpp +++ b/libs/ui/dialogs/KisSessionManagerDialog.cpp @@ -1,164 +1,203 @@ /* * Copyright (c) 2018 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 #include #include #include #include #include "KisSessionManagerDialog.h" +int KisSessionManagerDialog::refreshEventType = -1; + KisSessionManagerDialog::KisSessionManagerDialog(QWidget *parent) : QDialog(parent) { setupUi(this); + + // Register the custom event type that is used to defer UI updates + if (refreshEventType == -1) { + refreshEventType = QEvent::registerEventType(); + } connect(btnNew, SIGNAL(clicked()), this, SLOT(slotNewSession())); connect(btnRename, SIGNAL(clicked()), this, SLOT(slotRenameSession())); connect(btnSwitchTo, SIGNAL(clicked()), this, SLOT(slotSwitchSession())); connect(btnDelete, SIGNAL(clicked()), this, SLOT(slotDeleteSession())); connect(btnClose, SIGNAL(clicked()), this, SLOT(slotClose())); - m_model = KisResourceModelProvider::resourceModel(ResourceType::Sessions); lstSessions->setModel(m_model); lstSessions->setModelColumn(KisResourceModel::Name); connect(m_model, SIGNAL(beforeResourcesLayoutReset(QModelIndex)), this, SLOT(slotModelAboutToBeReset(QModelIndex))); connect(m_model, SIGNAL(afterResourcesLayoutReset()), this, SLOT(slotModelReset())); connect(lstSessions, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotSessionDoubleClicked(QModelIndex))); + + connect(lstSessions->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(slotModelSelectionChanged(QItemSelection, QItemSelection))); + updateButtons(); +} + +bool KisSessionManagerDialog::event(QEvent *event) +{ + if (event->type() == (QEvent::Type) refreshEventType) { + // Do the actual work of updating the button state when receiving a custom event + bool hasSelectedSession = getSelectedSession() != nullptr; + btnDelete->setEnabled(hasSelectedSession); + btnSwitchTo->setEnabled(hasSelectedSession); + btnRename->setEnabled(hasSelectedSession); + return true; + } else { + return QDialog::event(event); + } +} +void KisSessionManagerDialog::updateButtons() +{ + // Defer updating the buttons by posting a custom event with low priority to avoid locking against + // a non-recursive session lock that may be already held by the thread + QApplication::postEvent(this, new QEvent((QEvent::Type) refreshEventType), Qt::LowEventPriority); } void KisSessionManagerDialog::slotNewSession() { QString name; KisSessionResourceSP session(new KisSessionResource(QString())); KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); QString saveLocation = server->saveLocation(); QFileInfo fileInfo(saveLocation + name.split(" ").join("_") + session->defaultFileExtension()); bool fileOverwriteAccepted = false; while(!fileOverwriteAccepted) { name = QInputDialog::getText(this, i18n("Create session"), i18n("Session name:"), QLineEdit::Normal, name); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + session->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::Yes) fileOverwriteAccepted = true; } else { fileOverwriteAccepted = true; } } } session->setFilename(fileInfo.fileName()); session->setName(name); session->storeCurrentWindows(); server->addResource(session); KisPart::instance()->setCurrentSession(session); } void KisSessionManagerDialog::slotRenameSession() { QString name = QInputDialog::getText(this, i18n("Rename session"), i18n("New name:"), QLineEdit::Normal ); if (name.isNull() || name.isEmpty()) return; KisSessionResourceSP session = getSelectedSession(); if (!session) return; m_model->renameResource(session, name); } void KisSessionManagerDialog::slotSessionDoubleClicked(QModelIndex /*item*/) { slotSwitchSession(); slotClose(); } void KisSessionManagerDialog::slotSwitchSession() { KisSessionResourceSP session = getSelectedSession(); if (session) { bool closed = KisPart::instance()->closeSession(true); if (closed) { KisPart::instance()->restoreSession(session); } } } KisSessionResourceSP KisSessionManagerDialog::getSelectedSession() const { QModelIndex idx = lstSessions->currentIndex(); if (idx.isValid()) { KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); QString name = m_model->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); return server->resourceByName(name); } return nullptr; } void KisSessionManagerDialog::slotDeleteSession() { QModelIndex idx = lstSessions->currentIndex(); if (idx.isValid()) { m_model->removeResource(lstSessions->currentIndex()); } } void KisSessionManagerDialog::slotClose() { hide(); } void KisSessionManagerDialog::slotModelAboutToBeReset(QModelIndex) { QModelIndex idx = lstSessions->currentIndex(); if (idx.isValid()) { m_lastSessionId = m_model->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); } } void KisSessionManagerDialog::slotModelReset() { for (int i = 0; i < m_model->rowCount(); i++) { QModelIndex idx = m_model->index(i, 0); int id = m_model->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); if (id == m_lastSessionId) { lstSessions->setCurrentIndex(idx); } } + + updateButtons(); +} + +void KisSessionManagerDialog::slotModelSelectionChanged(QItemSelection selected, QItemSelection deselected) +{ + (void) selected; + (void) deselected; + + updateButtons(); } diff --git a/libs/ui/dialogs/KisSessionManagerDialog.h b/libs/ui/dialogs/KisSessionManagerDialog.h index 6e5f0a34f0..3602a0c904 100644 --- a/libs/ui/dialogs/KisSessionManagerDialog.h +++ b/libs/ui/dialogs/KisSessionManagerDialog.h @@ -1,59 +1,67 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISSESSIONMANAGERDIALOG_H #define KISSESSIONMANAGERDIALOG_H #include + #include "ui_wdgsessionmanager.h" #include class KisResourceModel; class KisSessionManagerDialog : public QDialog, Ui::DlgSessionManager { Q_OBJECT public: explicit KisSessionManagerDialog(QWidget *parent = nullptr); + +protected: + bool event(QEvent *event) override; private Q_SLOTS: void slotNewSession(); void slotRenameSession(); void slotSwitchSession(); void slotDeleteSession(); void slotSessionDoubleClicked(QModelIndex item); void slotClose(); void slotModelAboutToBeReset(QModelIndex); void slotModelReset(); + void slotModelSelectionChanged(QItemSelection selected, QItemSelection deselected); private: + void updateButtons(); KisSessionResourceSP getSelectedSession() const; int m_lastSessionId; KisResourceModel* m_model; + + static int refreshEventType; }; #endif diff --git a/libs/ui/kis_canvas_resource_provider.cpp b/libs/ui/kis_canvas_resource_provider.cpp index 7689a6b282..6d3dd42cdd 100644 --- a/libs/ui/kis_canvas_resource_provider.cpp +++ b/libs/ui/kis_canvas_resource_provider.cpp @@ -1,567 +1,561 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_canvas_resource_provider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "KisViewManager.h" #include "canvas/kis_canvas2.h" KisCanvasResourceProvider::KisCanvasResourceProvider(KisViewManager * view) : m_view(view) { m_fGChanged = true; - m_enablefGChange = true; // default to true, so that colour history is working without popup palette } KisCanvasResourceProvider::~KisCanvasResourceProvider() { disconnect(); // in case Qt gets confused } KoCanvasResourceProvider* KisCanvasResourceProvider::resourceManager() { return m_resourceManager; } void KisCanvasResourceProvider::setResourceManager(KoCanvasResourceProvider *resourceManager) { m_resourceManager = resourceManager; QVariant v; v.setValue(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8())); m_resourceManager->setResource(KoCanvasResourceProvider::ForegroundColor, v); v.setValue(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8())); m_resourceManager->setResource(KoCanvasResourceProvider::BackgroundColor, v); setCurrentCompositeOp(COMPOSITE_OVER); setMirrorHorizontal(false); setMirrorVertical(false); m_resourceManager->setResource(HdrExposure, 0.0); m_resourceManager->setResource(HdrGamma, 1.0); m_resourceManager->setResource(EffectiveZoom, 1.0); connect(m_resourceManager, SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); m_resourceManager->setResource(KoCanvasResourceProvider::ApplicationSpeciality, KoCanvasResourceProvider::NoAdvancedText); m_resourceManager->setResource(GamutMaskActive, false); } KoCanvasBase * KisCanvasResourceProvider::canvas() const { return m_view->canvasBase(); } KoColor KisCanvasResourceProvider::bgColor() const { return m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); } KoColor KisCanvasResourceProvider::fgColor() const { return m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); } float KisCanvasResourceProvider::HDRExposure() const { return static_cast(m_resourceManager->resource(HdrExposure).toDouble()); } void KisCanvasResourceProvider::setHDRExposure(float exposure) { m_resourceManager->setResource(HdrExposure, static_cast(exposure)); } float KisCanvasResourceProvider::HDRGamma() const { return static_cast(m_resourceManager->resource(HdrGamma).toDouble()); } void KisCanvasResourceProvider::setHDRGamma(float gamma) { m_resourceManager->setResource(HdrGamma, static_cast(gamma)); } KoPatternSP KisCanvasResourceProvider::currentPattern() const { if (m_resourceManager->hasResource(CurrentPattern)) { return m_resourceManager->resource(CurrentPattern).value(); } else { return 0; } } KoAbstractGradientSP KisCanvasResourceProvider::currentGradient() const { if (m_resourceManager->hasResource(CurrentGradient)) { return m_resourceManager->resource(CurrentGradient).value(); } else { return 0; } } KisImageWSP KisCanvasResourceProvider::currentImage() const { return m_view->image(); } KisNodeSP KisCanvasResourceProvider::currentNode() const { return m_view->activeNode(); } KoGamutMaskSP KisCanvasResourceProvider::currentGamutMask() const { if (m_resourceManager->hasResource(CurrentGamutMask)) { return m_resourceManager->resource(CurrentGamutMask).value(); } else { return nullptr; } } bool KisCanvasResourceProvider::gamutMaskActive() const { return m_resourceManager->resource(GamutMaskActive).toBool(); } KisPaintOpPresetSP KisCanvasResourceProvider::currentPreset() const { KisPaintOpPresetSP preset = m_resourceManager->resource(CurrentPaintOpPreset).value(); return preset; } void KisCanvasResourceProvider::setPaintOpPreset(const KisPaintOpPresetSP preset) { Q_ASSERT(preset->valid()); Q_ASSERT(!preset->paintOp().id().isEmpty()); Q_ASSERT(preset->settings()); if (!preset) return; dbgUI << "setPaintOpPreset" << preset->paintOp(); QVariant v; v.setValue(preset); m_resourceManager->setResource(CurrentPaintOpPreset, v); } KisPaintOpPresetSP KisCanvasResourceProvider::previousPreset() const { KisPaintOpPresetSP preset = m_resourceManager->resource(PreviousPaintOpPreset).value(); return preset; } void KisCanvasResourceProvider::setPreviousPaintOpPreset(const KisPaintOpPresetSP preset) { Q_ASSERT(preset->valid()); Q_ASSERT(!preset->paintOp().id().isEmpty()); Q_ASSERT(preset->settings()); if (!preset) return; dbgUI << "setPreviousPaintOpPreset" << preset->paintOp(); QVariant v; v.setValue(preset); m_resourceManager->setResource(PreviousPaintOpPreset, v); } void KisCanvasResourceProvider::slotPatternActivated(KoResourceSP res) { KoPatternSP pattern = res.dynamicCast(); QVariant v; v.setValue(pattern); m_resourceManager->setResource(CurrentPattern, v); emit sigPatternChanged(pattern); } void KisCanvasResourceProvider::slotGradientActivated(KoResourceSP res) { KoAbstractGradientSP gradient = res.dynamicCast(); QVariant v; v.setValue(gradient); m_resourceManager->setResource(CurrentGradient, v); emit sigGradientChanged(gradient); } void KisCanvasResourceProvider::setBGColor(const KoColor& c) { QVariant v; v.setValue(c); m_resourceManager->setResource(KoCanvasResourceProvider::BackgroundColor, v); emit sigBGColorChanged(c); } void KisCanvasResourceProvider::setFGColor(const KoColor& c) { m_fGChanged = true; QVariant v; v.setValue(c); m_resourceManager->setResource(KoCanvasResourceProvider::ForegroundColor, v); emit sigFGColorChanged(c); } void KisCanvasResourceProvider::slotSetFGColor(const KoColor& c) { setFGColor(c); } void KisCanvasResourceProvider::slotSetBGColor(const KoColor& c) { setBGColor(c); } void KisCanvasResourceProvider::slotNodeActivated(const KisNodeSP node) { QVariant v; v.setValue(KisNodeWSP(node)); m_resourceManager->setResource(CurrentKritaNode, v); emit sigNodeChanged(currentNode()); } void KisCanvasResourceProvider::slotImageSizeChanged() { if (KisImageWSP image = m_view->image()) { float fw = image->width() / image->xRes(); float fh = image->height() / image->yRes(); QSizeF postscriptSize(fw, fh); m_resourceManager->setResource(KoCanvasResourceProvider::PageSize, postscriptSize); } } void KisCanvasResourceProvider::slotOnScreenResolutionChanged() { KisImageWSP image = m_view->image(); KisCanvas2 *canvas = m_view->canvasBase(); if(!image || !canvas) return; qreal zoomX, zoomY; canvas->coordinatesConverter()->zoom(&zoomX, &zoomY); qreal scaleX = zoomX / image->xRes(); qreal scaleY = zoomY / image->yRes(); emit sigOnScreenResolutionChanged(scaleX, scaleY); } void KisCanvasResourceProvider::slotCanvasResourceChanged(int key, const QVariant & res) { if (key == KoCanvasResourceProvider::ForegroundColor || key == KoCanvasResourceProvider::BackgroundColor) { KoAbstractGradientSP resource = KoResourceServerProvider::instance()->gradientServer()->resourceByFilename("Foreground to Background"); if (resource) { KoStopGradientSP stopGradient = resource.dynamicCast(); if (stopGradient) { QList stops; stops << KoGradientStop(0.0, fgColor()) << KoGradientStop(1.0, KoColor(QColor(0, 0, 0, 0), fgColor().colorSpace())); stopGradient->setStops(stops); KoResourceServerProvider::instance()->gradientServer()->updateResource(resource); } } resource = KoResourceServerProvider::instance()->gradientServer()->resourceByFilename("Foreground to Transparent"); if (resource){ KoStopGradientSP stopGradient = resource.dynamicCast(); if (stopGradient) { QList stops; stops << KoGradientStop(0.0, fgColor()) << KoGradientStop(1.0, bgColor()); stopGradient->setStops(stops); KoResourceServerProvider::instance()->gradientServer()->updateResource(resource); } } } switch (key) { case(KoCanvasResourceProvider::ForegroundColor): m_fGChanged = true; emit sigFGColorChanged(res.value()); break; case(KoCanvasResourceProvider::BackgroundColor): emit sigBGColorChanged(res.value()); break; case(CurrentPattern): emit sigPatternChanged(res.value()); break; case(CurrentGradient): emit sigGradientChanged(res.value()); break; case(CurrentKritaNode) : emit sigNodeChanged(currentNode()); break; case (Opacity): { emit sigOpacityChanged(res.toDouble()); } default: ; // Do nothing }; } void KisCanvasResourceProvider::setCurrentCompositeOp(const QString& compositeOp) { m_resourceManager->setResource(CurrentCompositeOp, QVariant::fromValue(compositeOp)); } QString KisCanvasResourceProvider::currentCompositeOp() const { return m_resourceManager->resource(CurrentCompositeOp).value(); } bool KisCanvasResourceProvider::eraserMode() const { return m_resourceManager->resource(EraserMode).toBool(); } void KisCanvasResourceProvider::setEraserMode(bool value) { m_resourceManager->setResource(EraserMode, QVariant::fromValue(value)); } void KisCanvasResourceProvider::slotPainting() { - if (m_fGChanged && m_enablefGChange) { + if (m_fGChanged) { emit sigFGColorUsed(fgColor()); m_fGChanged = false; } } void KisCanvasResourceProvider::slotGamutMaskActivated(KoGamutMaskSP mask) { QVariant v; v.setValue(mask); m_resourceManager->setResource(CurrentGamutMask, v); m_resourceManager->setResource(GamutMaskActive, QVariant::fromValue(true)); emit sigGamutMaskChanged(mask); } void KisCanvasResourceProvider::slotGamutMaskUnset() { m_resourceManager->setResource(GamutMaskActive, QVariant::fromValue(false)); m_resourceManager->clearResource(CurrentGamutMask); emit sigGamutMaskUnset(); } void KisCanvasResourceProvider::slotGamutMaskPreviewUpdate() { emit sigGamutMaskPreviewUpdate(); } void KisCanvasResourceProvider::slotGamutMaskDeactivate() { m_resourceManager->setResource(GamutMaskActive, QVariant::fromValue(false)); emit sigGamutMaskDeactivated(); } -void KisCanvasResourceProvider::slotResetEnableFGChange(bool b) -{ - m_enablefGChange = b; -} - QList > KisCanvasResourceProvider::perspectiveGrids() const { return m_perspectiveGrids; } void KisCanvasResourceProvider::addPerspectiveGrid(KisAbstractPerspectiveGrid* grid) { m_perspectiveGrids.append(grid); } void KisCanvasResourceProvider::removePerspectiveGrid(KisAbstractPerspectiveGrid* grid) { m_perspectiveGrids.removeOne(grid); } void KisCanvasResourceProvider::clearPerspectiveGrids() { m_perspectiveGrids.clear(); } void KisCanvasResourceProvider::setMirrorHorizontal(bool mirrorHorizontal) { m_resourceManager->setResource(MirrorHorizontal, mirrorHorizontal); emit mirrorModeChanged(); } bool KisCanvasResourceProvider::mirrorHorizontal() const { return m_resourceManager->resource(MirrorHorizontal).toBool(); } void KisCanvasResourceProvider::setMirrorVertical(bool mirrorVertical) { m_resourceManager->setResource(MirrorVertical, mirrorVertical); emit mirrorModeChanged(); } bool KisCanvasResourceProvider::mirrorVertical() const { return m_resourceManager->resource(MirrorVertical).toBool(); } void KisCanvasResourceProvider::setMirrorHorizontalLock(bool isLocked) { m_resourceManager->setResource(MirrorHorizontalLock, isLocked); emit mirrorModeChanged(); } bool KisCanvasResourceProvider::mirrorHorizontalLock() { return m_resourceManager->resource(MirrorHorizontalLock).toBool(); } void KisCanvasResourceProvider::setMirrorVerticalLock(bool isLocked) { m_resourceManager->setResource(MirrorVerticalLock, isLocked); emit mirrorModeChanged(); } bool KisCanvasResourceProvider::mirrorVerticalHideDecorations() { return m_resourceManager->resource(MirrorVerticalHideDecorations).toBool(); } void KisCanvasResourceProvider::setMirrorVerticalHideDecorations(bool hide) { m_resourceManager->setResource(MirrorVerticalHideDecorations, hide); emit mirrorModeChanged(); } bool KisCanvasResourceProvider::mirrorHorizontalHideDecorations() { return m_resourceManager->resource(MirrorHorizontalHideDecorations).toBool(); } void KisCanvasResourceProvider::setMirrorHorizontalHideDecorations(bool hide) { m_resourceManager->setResource(MirrorHorizontalHideDecorations, hide); emit mirrorModeChanged(); } bool KisCanvasResourceProvider::mirrorVerticalLock() { return m_resourceManager->resource(MirrorVerticalLock).toBool(); } void KisCanvasResourceProvider::mirrorVerticalMoveCanvasToCenter() { emit moveMirrorVerticalCenter(); } void KisCanvasResourceProvider::mirrorHorizontalMoveCanvasToCenter() { emit moveMirrorHorizontalCenter(); } void KisCanvasResourceProvider::setOpacity(qreal opacity) { m_resourceManager->setResource(Opacity, opacity); } qreal KisCanvasResourceProvider::opacity() const { return m_resourceManager->resource(Opacity).toReal(); } void KisCanvasResourceProvider::setFlow(qreal flow) { m_resourceManager->setResource(Flow, flow); } qreal KisCanvasResourceProvider::flow() const { return m_resourceManager->resource(Flow).toReal(); } void KisCanvasResourceProvider::setSize(qreal size) { m_resourceManager->setResource(Size, size); } qreal KisCanvasResourceProvider::size() const { return m_resourceManager->resource(Size).toReal(); } void KisCanvasResourceProvider::setGlobalAlphaLock(bool lock) { m_resourceManager->setResource(GlobalAlphaLock, lock); } bool KisCanvasResourceProvider::globalAlphaLock() const { return m_resourceManager->resource(GlobalAlphaLock).toBool(); } void KisCanvasResourceProvider::setDisablePressure(bool value) { m_resourceManager->setResource(DisablePressure, value); } bool KisCanvasResourceProvider::disablePressure() const { return m_resourceManager->resource(DisablePressure).toBool(); } void KisCanvasResourceProvider::notifyLoadingWorkspace(KisWorkspaceResourceSP workspace) { emit sigLoadingWorkspace(workspace); } void KisCanvasResourceProvider::notifySavingWorkspace(KisWorkspaceResourceSP workspace) { emit sigSavingWorkspace(workspace); } diff --git a/libs/ui/kis_canvas_resource_provider.h b/libs/ui/kis_canvas_resource_provider.h index 2772f5342a..24e6c57c1d 100644 --- a/libs/ui/kis_canvas_resource_provider.h +++ b/libs/ui/kis_canvas_resource_provider.h @@ -1,253 +1,238 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CANVAS_RESOURCE_PROVIDER_H_ #define KIS_CANVAS_RESOURCE_PROVIDER_H_ #include #include #include #include #include #include "kis_types.h" #include "kritaui_export.h" #include #include #include #include class KoColorProfile; class KoAbstractGradient; class KoCanvasBase; class KisViewManager; class KisFilterConfiguration; #include /** * KisCanvasResourceProvider contains the per-window current settings that * influence painting, like paintop, color, gradients and so on. */ class KRITAUI_EXPORT KisCanvasResourceProvider : public QObject { Q_OBJECT public: enum Resources { HdrExposure = KoCanvasResourceProvider::KritaStart + 1, CurrentPattern, CurrentGamutMask, GamutMaskActive, CurrentGradient, CurrentDisplayProfile, CurrentKritaNode, CurrentPaintOpPreset, CurrentGeneratorConfiguration, CurrentCompositeOp, CurrentEffectiveCompositeOp, LodAvailability, ///<-user choice LodSizeThreshold, ///<-user choice LodSizeThresholdSupported, ///<-paintop property EffectiveLodAvailablility, ///<- a superposition of user choice, threshold and paintop traits EraserMode, MirrorHorizontal, MirrorVertical, MirrorHorizontalLock, MirrorVerticalLock, MirrorVerticalHideDecorations, MirrorHorizontalHideDecorations, Opacity, Flow, Size, HdrGamma, GlobalAlphaLock, DisablePressure, PreviousPaintOpPreset, EffectiveZoom ///<-Used only by painting tools for non-displaying purposes }; KisCanvasResourceProvider(KisViewManager * view); ~KisCanvasResourceProvider() override; void setResourceManager(KoCanvasResourceProvider *resourceManager); KoCanvasResourceProvider* resourceManager(); KoCanvasBase * canvas() const; KoColor bgColor() const; void setBGColor(const KoColor& c); KoColor fgColor() const; void setFGColor(const KoColor& c); float HDRExposure() const; void setHDRExposure(float exposure); float HDRGamma() const; void setHDRGamma(float gamma); bool eraserMode() const; void setEraserMode(bool value); KoPatternSP currentPattern() const; KoAbstractGradientSP currentGradient() const; KisImageWSP currentImage() const; KisNodeSP currentNode() const; KoGamutMaskSP currentGamutMask() const; bool gamutMaskActive() const; KisPaintOpPresetSP currentPreset() const; void setPaintOpPreset(const KisPaintOpPresetSP preset); KisPaintOpPresetSP previousPreset() const; void setPreviousPaintOpPreset(const KisPaintOpPresetSP preset); void setCurrentCompositeOp(const QString& compositeOp); QString currentCompositeOp() const; QList > perspectiveGrids() const; void addPerspectiveGrid(KisAbstractPerspectiveGrid*); void removePerspectiveGrid(KisAbstractPerspectiveGrid*); void clearPerspectiveGrids(); void setMirrorHorizontal(bool mirrorHorizontal); bool mirrorHorizontal() const; void setMirrorVertical(bool mirrorVertical); bool mirrorVertical() const; // options for horizontal and vertical mirror toolbar void setMirrorHorizontalLock(bool isLocked); bool mirrorHorizontalLock(); void setMirrorVerticalLock(bool isLocked); bool mirrorVerticalLock(); void setMirrorVerticalHideDecorations(bool hide); bool mirrorVerticalHideDecorations(); void setMirrorHorizontalHideDecorations(bool hide); bool mirrorHorizontalHideDecorations(); void mirrorVerticalMoveCanvasToCenter(); void mirrorHorizontalMoveCanvasToCenter(); void setOpacity(qreal opacity); qreal opacity() const; void setFlow(qreal opacity); qreal flow() const; void setSize(qreal size); qreal size() const; void setGlobalAlphaLock(bool lock); bool globalAlphaLock() const; void setDisablePressure(bool value); bool disablePressure() const; ///Notify that the workspace is saved and settings should be saved to it void notifySavingWorkspace(KisWorkspaceResourceSP workspace); ///Notify that the workspace is loaded and settings can be read void notifyLoadingWorkspace(KisWorkspaceResourceSP workspace); public Q_SLOTS: void slotSetFGColor(const KoColor& c); void slotSetBGColor(const KoColor& c); void slotPatternActivated(KoResourceSP pattern); void slotGradientActivated(KoResourceSP gradient); void slotNodeActivated(const KisNodeSP node); void slotPainting(); void slotGamutMaskActivated(KoGamutMaskSP mask); void slotGamutMaskUnset(); void slotGamutMaskPreviewUpdate(); void slotGamutMaskDeactivate(); /** * Set the image size in pixels. The resource provider will store * the image size in postscript points. */ // FIXME: this slot doesn't catch the case when image resolution is changed void slotImageSizeChanged(); void slotOnScreenResolutionChanged(); - // This is a flag to handle a bug: - // If pop up palette is visible and a new colour is selected, the new colour - // will be added when the user clicks on the canvas to hide the palette - // In general, we want to be able to store recent color if the pop up palette - // is not visible - void slotResetEnableFGChange(bool); - private Q_SLOTS: void slotCanvasResourceChanged(int key, const QVariant & res); Q_SIGNALS: void sigFGColorChanged(const KoColor &); void sigBGColorChanged(const KoColor &); void sigGradientChanged(KoAbstractGradientSP); void sigPatternChanged(KoPatternSP); void sigNodeChanged(const KisNodeSP); void sigDisplayProfileChanged(const KoColorProfile *); void sigFGColorUsed(const KoColor&); void sigOnScreenResolutionChanged(qreal scaleX, qreal scaleY); void sigOpacityChanged(qreal); void sigSavingWorkspace(KisWorkspaceResourceSP workspace); void sigLoadingWorkspace(KisWorkspaceResourceSP workspace); void mirrorModeChanged(); void moveMirrorVerticalCenter(); void moveMirrorHorizontalCenter(); void sigGamutMaskChanged(KoGamutMaskSP mask); void sigGamutMaskUnset(); void sigGamutMaskPreviewUpdate(); void sigGamutMaskDeactivated(); private: KisViewManager * m_view; KoCanvasResourceProvider *m_resourceManager; bool m_fGChanged; QList > m_perspectiveGrids; - - // This is a flag to handle a bug: - // If pop up palette is visible and a new colour is selected, the new colour - // will be added when the user clicks on the canvas to hide the palette - // In general, we want to be able to store recent color if the pop up palette - // is not visible - bool m_enablefGChange; - }; #endif diff --git a/libs/ui/kis_favorite_resource_manager.h b/libs/ui/kis_favorite_resource_manager.h index 1d3c75dafb..35a062e97b 100644 --- a/libs/ui/kis_favorite_resource_manager.h +++ b/libs/ui/kis_favorite_resource_manager.h @@ -1,134 +1,127 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_FAVORITE_RESOURCE_MANAGER_H #define KIS_FAVORITE_RESOURCE_MANAGER_H #include #include #include #include #include "KoResourceServerObserver.h" #include #include "KisTagFilterResourceProxyModel.h" #include #include #include class QString; class KisPaintopBox; class KisPaintOpPreset; class KisFavoriteResourceManager : public QObject, public KoResourceServerObserver { Q_OBJECT public: KisFavoriteResourceManager(KisPaintopBox *paintopBox); ~KisFavoriteResourceManager() override; void unsetResourceServer() override; QList favoritePresetImages(); QVector favoritePresetNamesList(); void setCurrentTag(const KisTagSP tag); int numFavoritePresets(); void updateFavoritePresets(); int recentColorsTotal(); const KoColor& recentColorAt(int pos); // Reimplemented from KoResourceServerObserver void removingResource(QSharedPointer resource) override; void resourceAdded(QSharedPointer resource) override; void resourceChanged(QSharedPointer resource) override; void syncTaggedResourceView() override; void syncTagAddition(const QString& tag) override; void syncTagRemoval(const QString& tag) override; //BgColor; KoColor bgColor() const; Q_SIGNALS: void sigSetFGColor(const KoColor& c); void sigSetBGColor(const KoColor& c); - // This is a flag to handle a bug: - // If pop up palette is visible and a new colour is selected, the new colour - // will be added when the user clicks on the canvas to hide the palette - // In general, we want to be able to store recent colours if the pop up palette - // is not visible - void sigEnableChangeColor(bool b); - void sigChangeFGColorSelector(const KoColor&); void setSelectedColor(int); void updatePalettes(); void hidePalettes(); public Q_SLOTS: void slotChangeActivePaintop(int); /*update the priority of a colour in m_colorList, used only by m_popupPalette*/ void slotUpdateRecentColor(int); /*add a colour to m_colorList, used by KisCanvasResourceProvider*/ void slotAddRecentColor(const KoColor&); void slotChangeFGColorSelector(KoColor c); void slotSetBGColor(const KoColor c); private Q_SLOTS: void configChanged(); private: void init(); KisPaintopBox *m_paintopBox; class ColorDataList; ColorDataList *m_colorList; void saveFavoritePresets(); KoColor m_bgColor; KisTagSP m_currentTag; bool m_initialized; int m_maxPresets; KisTagModel* m_tagModel; KisTagFilterResourceProxyModel* m_resourcesProxyModel; KisResourceModel* m_resourceModel; }; #endif diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index 4cb2bb7bc3..234f2c7b5d 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1399 +1,1398 @@ /* * 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 "KisResourceServerProvider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "KisPopupButton.h" #include "widgets/kis_iconwidget.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_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 "KisHighlightedToolButton.h" #include KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->canvasResourceProvider()) , 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(true); 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 KisIconWidget(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); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); toolbarMenuXMirror->addAction(lockActionX); moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); toolbarMenuXMirror->addAction(moveToCenterActionX); // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); toolbarMenuYMirror->addAction(lockActionY); moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); toolbarMenuYMirror->addAction(moveToCenterActionY); // create horizontal and vertical mirror buttons m_hMirrorButton = new KisHighlightedToolButton(this); int menuPadding = 10; m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_hMirrorButton->setCheckable(true); m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action"); m_hMirrorButton->setDefaultAction(m_hMirrorAction); m_hMirrorButton->setMenu(toolbarMenuXMirror); m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); m_vMirrorButton = new KisHighlightedToolButton(this); m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_vMirrorButton->setCheckable(true); m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action"); m_vMirrorButton->setDefaultAction(m_vMirrorAction); m_vMirrorButton->setMenu(toolbarMenuYMirror); m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); // add connections for horizontal and mirrror buttons connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool))); connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool))); connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX())); connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY())); connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool))); connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool))); const bool sliderLabels = cfg.sliderLabels(); int sliderWidth; if (sliderLabels) { sliderWidth = 150 * logicalDpiX() / 96; } else { sliderWidth = 120 * logicalDpiX() / 96; } for (int i = 0; i < 3; ++i) { m_sliderChooser[i] = new KisWidgetChooser(i + 1); KisDoubleSliderSpinBox* slOpacity; KisDoubleSliderSpinBox* slFlow; KisDoubleSliderSpinBox* slSize; if (sliderLabels) { slOpacity = m_sliderChooser[i]->addWidget("opacity"); slFlow = m_sliderChooser[i]->addWidget("flow"); slSize = m_sliderChooser[i]->addWidget("size"); slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:"))); slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:"))); slSize->setPrefix(QString("%1 ").arg(i18n("Size:"))); } else { slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:")); slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:")); slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:")); } slOpacity->setRange(0, 100, 0); slOpacity->setValue(100); slOpacity->setSingleStep(5); slOpacity->setSuffix(i18n("%")); slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width())); slOpacity->setFixedHeight(iconsize); slOpacity->setBlockUpdateSignalOnDrag(true); slFlow->setRange(0, 100, 0); slFlow->setValue(100); slFlow->setSingleStep(5); slFlow->setSuffix(i18n("%")); slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width())); slFlow->setFixedHeight(iconsize); slFlow->setBlockUpdateSignalOnDrag(true); slSize->setRange(0.01, 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->createBlendmodeActions()) { 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); 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(ResourceType::Workspaces, action); view->actionCollection()->addAction(ResourceType::Workspaces, action); action->setDefaultWidget(m_workspaceWidget); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopup = new KisToolOptionsPopup(); m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup); m_toolOptionsPopup->switchDetached(false); } m_savePresetWidget = new KisPresetSaveWidget(this); m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); m_presetsChooserPopup->setMinimumHeight(550); m_presetsChooserPopup->setMinimumWidth(450); m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup); m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); slotNodeChanged(view->activeNode()); // Get all the paintops QList keys = KisPaintOpRegistry::instance()->keys(); QList factoryList; Q_FOREACH (const QString & paintopId, keys) { factoryList.append(KisPaintOpRegistry::instance()->get(paintopId)); } m_presetsPopup->setPaintOpList(factoryList); connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString))); connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset())); connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResourceSP )), SLOT(resourceSelected(KoResourceSP ))); 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_presetsPopup, SIGNAL(createPresetFromScratch(QString)), this, SLOT(slotCreatePresetFromScratch(QString))); connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResourceSP )) , SLOT(resourceSelected(KoResourceSP ))); connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResourceSP )) , SLOT(resourceSelected(KoResourceSP ))); connect(m_resourceProvider , SIGNAL(sigNodeChanged(KisNodeSP)) , SLOT(slotNodeChanged(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))); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); 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())); 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())); connect(m_resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); connect(m_resourceProvider->resourceManager(), SIGNAL(canvasResourceChangeAttempted(int,QVariant)), this, SLOT(slotCanvasResourceChangeAttempted(int,QVariant))); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); findDefaultPresets(); } KisPaintopBox::~KisPaintopBox() { KisConfig cfg(false); QMapIterator iter(m_tabletToolMap); while (iter.hasNext()) { iter.next(); if ((iter.key().pointer) == QTabletEvent::Eraser) { cfg.writeEntry(QString("LastEraser") , iter.value().preset->name()); } else { cfg.writeEntry(QString("LastPreset"), iter.value().preset->name()); } } // Do not delete the widget, since 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(KoResourceSP resource) { KisPaintOpPresetSP preset = resource.dynamicCast(); 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(KoResourceSP resource) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_presetsPopup->setCreatingBrushFromScratch(false); // show normal UI elements when we are not creating // qDebug() << ">>>>>>>>>>>>>>>" << resource // << (resource ? resource->name() : "") // << (resource ? QString("%1").arg(resource->valid()) : "") // << (resource ? QString("%1").arg(resource->filename()) : ""); KisPaintOpPresetSP preset = resource.dynamicCast(); if (preset && preset->valid() && preset != m_resourceProvider->currentPreset()) { qWarning() << "Preset reloading if presets are dirty is broken"; // if (!preset->settings()->isLoadable()) { // return; // } // if (!m_dirtyPresetsEnabled) { // KisSignalsBlocker blocker(m_optionWidget); // Q_UNUSED(blocker) // if (!preset->load()) { // qWarning() << "failed to load the preset."; // } // } dbgResources << "resourceSelected: preset" << preset << (preset ? QString("%1").arg(preset->valid()) : ""); 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 m_brushEditorPopupButton->setThumbnail(preset->image()); 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"; } m_currCompositeOpID = preset->settings()->paintOpCompositeOp(); updateCompositeOp(m_currCompositeOpID); } void KisPaintopBox::slotUpdateOptionsWidgetPopup() { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); // This happens when we have a new brush engine for which no default preset exists yet. if (!preset) return; KIS_SAFE_ASSERT_RECOVER_RETURN(preset); KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_optionWidget->setConfigurationSafe(preset->settings()); m_presetsPopup->resourceSelected(preset); 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(KisGlobalResourcesInterface::instance())) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp, KisGlobalResourcesInterface::instance()); } 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_resourceProvider->eraserMode()) { m_eraseModeButton->setChecked(true); } else { m_eraseModeButton->setChecked(false); } } else if (!node) { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); m_currCompositeOpID = compositeOpID; } } 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); if (sliderID == "opacity" || sliderID == "flow") { // opacity and flows UI stored at 0-100% slider->setValue(value*100); } else { slider->setValue(value); // brush size } } } 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(true); KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset; if (inputDevice.pointer() == QTabletEvent::Eraser) { preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), m_eraserName)); } else { preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), m_defaultPresetName)); //if (preset) //qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId(); //else //qDebug() << "no preset found for" << inputDevice.uniqueTabletId(); } if (!preset) { preset = rserver->resourceByName(m_defaultPresetName); } 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::slotCreatePresetFromScratch(QString paintop) { //First try to select an available default preset for that engine. If it doesn't exist, then //manually set the engine to use a new preset. KoID id(paintop, KisPaintOpRegistry::instance()->get(paintop)->name()); KisPaintOpPresetSP preset = defaultPreset(id); slotSetPaintop(paintop); // change the paintop settings area and update the UI if (!preset) { m_presetsPopup->setCreatingBrushFromScratch(true); // disable UI elements while creating from scratch preset = m_resourceProvider->currentPreset(); } else { m_resourceProvider->setPaintOpPreset(preset); preset->setOptionsWidget(m_optionWidget); } m_presetsPopup->resourceSelected(preset); // this helps update the UI on the brush editor } void KisPaintopBox::slotCanvasResourceChangeAttempted(int key, const QVariant &value) { Q_UNUSED(value); if (key == KoCanvasResourceProvider::ForegroundColor) { slotUnsetEraseMode(); } } void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value) { if (m_viewManager) { sender()->blockSignals(true); KisPaintOpPresetSP preset = m_viewManager->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) { QString compositeOp = preset->settings()->getString("CompositeOp"); updateCompositeOp(compositeOp); resourceSelected(preset); } if (key == KisCanvasResourceProvider::CurrentPaintOpPreset) { /** * 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()); } if (key == KisCanvasResourceProvider::MirrorHorizontal) { m_hMirrorAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::MirrorVertical) { m_vMirrorAction->setChecked(value.toBool()); } sender()->blockSignals(false); } } void KisPaintopBox::slotSetupDefaultPreset() { KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp()); preset->setOptionsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); // tell the brush editor that the resource has changed // so it can update everything m_presetsPopup->resourceSelected(preset); } 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); // flow and opacity are shown as 0-100% on the UI, but their data is actually 0-1. Convert those two values // back for further work qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value()/100; qreal flow = m_sliderChooser[n]->getWidget("flow")->value()/100; 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 won't 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()); } 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); 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; m_presetsEnabled = true; } else { setWidgetState(DISABLE_PRESETS); m_presetsEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) { setWidgetState(ENABLE_SIZE | ENABLE_FLOW); } else { setWidgetState(DISABLE_SIZE | DISABLE_FLOW); } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; QVector presets = m_favoriteResourceManager->favoritePresetNamesList(); for (int i=0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == presets[i]) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } //floating message should have least 2 lines, otherwise //preset thumbnail will be too small to distinguish //(because size of image on floating message depends on amount of lines in msg) m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); return; } } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; QVector presets = m_favoriteResourceManager->favoritePresetNamesList(); for(int i = 0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset()->name() == presets[i]) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); return; } } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { //qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset(); setCurrentPaintop(m_resourceProvider->previousPreset()); m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); } } void KisPaintopBox::slotUnsetEraseMode() { m_eraseAction->setChecked(false); } void KisPaintopBox::slotToggleAlphaLockMode(bool checked) { if (checked) { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked")); } else { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked")); } m_resourceProvider->setGlobalAlphaLock(checked); } void KisPaintopBox::slotDisablePressureMode(bool checked) { if (checked) { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureAction->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(); QSharedPointer preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name()); if (preset) { preset->load(KisGlobalResourcesInterface::instance()); } if (m_resourceProvider->currentPreset() != preset) { m_resourceProvider->setPaintOpPreset(preset); } else { /** * HACK ALERT: here we emit a private signal from the resource manager to * ensure that all the subscribers of resource-changed signal got the * notification. That is especially important for * KisPaintopTransformationConnector. See bug 392622. */ emit m_resourceProvider->resourceManager()->canvasResourceChanged( KisCanvasResourceProvider::CurrentPaintOpPreset, QVariant::fromValue(preset)); } } 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 the 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); QStringList preserveProperties; preserveProperties << "lodUserAllowed"; preserveProperties << "lodSizeThreshold"; // clear all the properties before dumping the stuff into the preset, // some of the options add the values incrementally // (e.g. KisPaintOpUtils::RequiredBrushFilesListTag), therefore they // may add up if we pass the same preset multiple times preset->settings()->resetSettings(preserveProperties); 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()); // 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(); { KisResourceDirtyStateSaver dirtySaver(preset); 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"); } } } } void KisPaintopBox::slotDirtyPresetToggled(bool value) { if (!value) { slotReloadPreset(); m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset()); m_presetsPopup->updateViewSettings(); } m_dirtyPresetsEnabled = value; KisConfig cfg(false); cfg.setUseDirtyPresets(m_dirtyPresetsEnabled); } void KisPaintopBox::slotEraserBrushSizeToggled(bool value) { m_eraserBrushSizeEnabled = value; KisConfig cfg(false); cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled); } void KisPaintopBox::slotEraserBrushOpacityToggled(bool value) { m_eraserBrushOpacityEnabled = value; KisConfig cfg(false); cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled); } void KisPaintopBox::slotUpdateSelectionIcon() { m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical")); KisConfig cfg(true); 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_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureAction->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(); } void KisPaintopBox::findDefaultPresets() { KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); m_eraserName = "eraser_circle"; m_defaultPresetName = "basic_tip_default"; KisResourceModel *resourceModel = rserver->resourceModel(); for (int i = 0; i < resourceModel->rowCount(); i++) { QModelIndex idx = resourceModel->index(i, 0); QString resourceName = idx.data(Qt::UserRole + KisResourceModel::Name).toString().toLower(); QString fileName = idx.data(Qt::UserRole + KisResourceModel::Filename).toString().toLower(); if (resourceName.contains("eraser_circle")) { m_eraserName = resourceName; } else if (resourceName.contains("eraser") || fileName.contains("eraser")) { m_eraserName = resourceName; } if (resourceName.contains("basic_tip_default")) { m_defaultPresetName = resourceName; } else if (resourceName.contains("default") || fileName.contains("default")) { m_defaultPresetName = resourceName; } } } diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index 743ddca690..b0a68d6f70 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,1012 +1,989 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 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 version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_config.h" #include "kis_popup_palette.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include "kis_signal_compressor.h" #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include "kis_signals_blocker.h" #include "kis_canvas_controller.h" #include "kis_acyclic_signal_connector.h" #include #include "KisMouseClickEater.h" class PopupColorTriangle : public KoTriangleColorSelector { public: PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) : KoTriangleColorSelector(displayRenderer, parent) , m_dragging(false) { } ~PopupColorTriangle() override {} void tabletEvent(QTabletEvent* event) override { event->accept(); QMouseEvent* mouseEvent = 0; // this will tell the pop-up palette widget to close if(event->button() == Qt::RightButton) { emit requestCloseContainer(); } // ignore any tablet events that are done with the right click // Tablet move events don't return a "button", so catch that too if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove) { switch (event->type()) { case QEvent::TabletPress: mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = true; mousePressEvent(mouseEvent); break; case QEvent::TabletMove: mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), (m_dragging) ? Qt::LeftButton : Qt::NoButton, (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); mouseMoveEvent(mouseEvent); break; case QEvent::TabletRelease: mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = false; mouseReleaseEvent(mouseEvent); break; default: break; } } delete mouseEvent; } private: bool m_dragging; }; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_coordinatesConverter(coordinatesConverter) , m_viewManager(viewManager) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_acyclicConnector(new KisAcyclicSignalConnector(this)) , m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this)) { // some UI controls are defined and created based off these variables const int borderWidth = 3; if (KisConfig(true).readEntry("popuppalette/usevisualcolorselector", false)) { KisVisualColorSelector *selector = new KisVisualColorSelector(this); selector->setAcceptTabletEvents(true); m_triangleColorSelector = selector; } else { m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide())); } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_popupPaletteSize - 2*m_triangleColorSelector->x(), m_popupPaletteSize - 2*m_triangleColorSelector->y()); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); /** * Tablet support code generates a spurious right-click right after opening * the window, so we should ignore it. Next right-click will be used for * closing the popup palette */ this->installEventFilter(m_clicksEater); m_triangleColorSelector->installEventFilter(m_clicksEater); QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(slotDisplayConfigurationChanged())); m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), this, SLOT(slotExternalFgColorChanged(KoColor))); m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); - // This is used to handle a bug: - // If pop up palette is visible and a new colour is selected, the new colour - // will be added when the user clicks on the canvas to hide the palette - // In general, we want to be able to store recent color if the pop up palette - // is not visible - m_timer.setSingleShot(true); - connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer())); - connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor())); - connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool))); - setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setFixedHeight(int(m_popupPaletteSize)); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg(true); m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setFixedSize(35, 35); mirrorMode->setToolTip(i18n("Mirror Canvas")); mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas")); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setToolTip(i18n("Canvas Only")); canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only")); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); zoomSliderMinValue = 10; // set in % zoomSliderMaxValue = 200; // set in % zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed())); connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased())); slotUpdateIcons(); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); opacityChange = new QGraphicsOpacityEffect(this); setGraphicsEffect(opacityChange); // Prevent tablet events from being captured by the canvas setAttribute(Qt::WA_NoMousePropagation, true); } void KisPopupPalette::slotDisplayConfigurationChanged() { // Visual Color Selector picks up color space from input KoColor col = m_viewManager->canvasResourceProvider()->fgColor(); const KoColorSpace *paintingCS = m_displayRenderer->getPaintingColorSpace(); //hack to get around cmyk for now. if (paintingCS->colorChannelCount()>3) { paintingCS = KoColorSpaceRegistry::instance()->rgb8(); } m_triangleColorSelector->slotSetColorSpace(paintingCS); m_triangleColorSelector->slotSetColor(col); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { m_triangleColorSelector->slotSetColor(color); } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } -void KisPopupPalette::slotTriggerTimer() -{ - m_timer.start(750); -} - -void KisPopupPalette::slotEnableChangeFGColor() -{ - emit sigEnableChangeFGColor(true); -} - void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::slotZoomSliderPressed() { m_isZoomingCanvas = true; } void KisPopupPalette::slotZoomSliderReleased() { m_isZoomingCanvas = false; } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::slotUpdateIcons() { this->setPalette(qApp->palette()); for(int i=0; ichildren().size(); i++) { QWidget *w = qobject_cast(this->children().at(i)); if (w) { w->setPalette(qApp->palette()); } } zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon()); m_brushHud->updateIcons(); m_settingsButton->setIcon(KisIconUtils::loadIcon("tag")); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg(false); cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within // the bounds and cause the canvas to jump between the slider's min and max if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){ KisSignalsBlocker b(zoomCanvasSlider); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider } - emit sigEnableChangeFGColor(!show); - } else { - emit sigTriggerTimer(); } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); // painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); // painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); // painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); // painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } // painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } // painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) { opacityChange->setOpacity(0.4); } else { opacityChange->setOpacity(1.0); } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; int canvasIndicatorMiddle = canvasIndicatorSize / 2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetNamesList().at(pos)); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { KisTagModel *model = KisTagModelProvider::tagModel(ResourceType::PaintOpPresets); QVector tags; for (int i = 0; i < model->rowCount(); ++i) { QModelIndex idx = model->index(i, 0); tags << model->data(idx, Qt::DisplayRole).toString(); } //std::sort(tags.begin(), tags.end()); if (!tags.isEmpty()) { QMenu menu; Q_FOREACH (const QString& tag, tags) { menu.addAction(tag); } QAction *action = menu.exec(QCursor::pos()); if (action) { for (int i = 0; i < model->rowCount(); ++i) { QModelIndex idx = model->index(i, 0); if (model->data(idx, Qt::DisplayRole).toString() == action->text()) { m_resourceManager->setCurrentTag(model->tagForIndex(idx)); break; } } } } else { QWhatsThis::showText(QCursor::pos(), i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); } } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction *action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } // also move the zoom slider to 100% position so they are in sync zoomCanvasSlider->setValue(100); } void KisPopupPalette::tabletEvent(QTabletEvent *event) { event->ignore(); } void KisPopupPalette::showEvent(QShowEvent *event) { m_clicksEater->reset(); QWidget::showEvent(event); } void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); if (event->buttons() == Qt::NoButton && event->button() == Qt::RightButton) { showPopupPalette(false); return; } m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal radians = qDegreesToRadians((360.0/10)/2); qreal maxRadius = (m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)))-2; radians = qDegreesToRadians(angleSlice/2); qreal presetRadius = m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)); //If we assume that circles will mesh like a hexagonal grid, then 3.5r is the size of two hexagons interlocking. qreal length = m_colorHistoryOuterRadius + presetRadius; // can we can fit in a second row? We don't want the preset icons to get too tiny. if (maxRadius > presetRadius) { //redo all calculations assuming a second row. if (numSlots() % 2) { angleSlice = 360.0/(numSlots()+1); startingAngle = -(index * angleSlice) + 90; } if (numSlots() != m_cachedNumSlots){ qreal tempRadius = presetRadius; qreal distance = 0; do{ tempRadius+=0.1; // Calculate the XY of two adjectant circles using this tempRadius. qreal length1 = m_colorHistoryOuterRadius + tempRadius; qreal length2 = m_colorHistoryOuterRadius + ((maxRadius*2)-tempRadius); qreal pathX1 = length1 * qCos(qDegreesToRadians(startingAngle)) - tempRadius; qreal pathY1 = -(length1) * qSin(qDegreesToRadians(startingAngle)) - tempRadius; qreal startingAngle2 = -(index+1 * angleSlice) + 90; qreal pathX2 = length2 * qCos(qDegreesToRadians(startingAngle2)) - tempRadius; qreal pathY2 = -(length2) * qSin(qDegreesToRadians(startingAngle2)) - tempRadius; // Use Pythagorean Theorem to calculate the distance between these two values. qreal m1 = pathX2-pathX1; qreal m2 = pathY2-pathY1; distance = sqrt((m1*m1)+(m2*m2)); } //As long at there's more distance than the radius of the two presets, continue increasing the radius. while((tempRadius+1)*2 < distance); m_cachedRadius = tempRadius; } m_cachedNumSlots = numSlots(); presetRadius = m_cachedRadius; length = m_colorHistoryOuterRadius + presetRadius; if (index % 2) { length = m_colorHistoryOuterRadius + ((maxRadius*2)-presetRadius); } } QPainterPath path; qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config(true); return qMax(config.favoritePresets(), 10); } diff --git a/libs/ui/kis_popup_palette.h b/libs/ui/kis_popup_palette.h index 51732f0e29..8938c7732b 100644 --- a/libs/ui/kis_popup_palette.h +++ b/libs/ui/kis_popup_palette.h @@ -1,191 +1,179 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2016 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 version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_POPUP_PALETTE_H #define KIS_POPUP_PALETTE_H #include #include #include #include #include "KisViewManager.h" #include "kactioncollection.h" #include "kis_tool_button.h" #include "KisHighlightedToolButton.h" #include class KisFavoriteResourceManager; class QWidget; class KoColor; class KoTriangleColorSelector; class KisSignalCompressor; class KisBrushHud; class KisRoundHudButton; class KisCanvasResourceProvider; class KisVisualColorSelector; class KisAcyclicSignalConnector; class KisMouseClickEater; class KisPopupPalette : public QWidget { Q_OBJECT Q_PROPERTY(int hoveredPreset READ hoveredPreset WRITE setHoveredPreset) Q_PROPERTY(int hoveredColor READ hoveredColor WRITE setHoveredColor) Q_PROPERTY(int selectedColor READ selectedColor WRITE setSelectedColor) public: KisPopupPalette(KisViewManager*, KisCoordinatesConverter* ,KisFavoriteResourceManager*, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent = 0); ~KisPopupPalette() override; QSize sizeHint() const override; void showPopupPalette(const QPoint&); void showPopupPalette(bool b); //functions to set up selectedBrush void setSelectedBrush(int x); int selectedBrush() const; //functions to set up selectedColor void setSelectedColor(int x); int selectedColor() const; void setParent(QWidget *parent); void tabletEvent(QTabletEvent *event) override; protected: void showEvent(QShowEvent *event) override; void paintEvent(QPaintEvent*) override; void resizeEvent(QResizeEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void mousePressEvent(QMouseEvent*) override; //functions to calculate index of favorite brush or recent color in array //n is the total number of favorite brushes or recent colors int calculateIndex(QPointF, int n); int calculatePresetIndex(QPointF, int n); //functions to set up hoveredBrush void setHoveredPreset(int x); int hoveredPreset() const; //functions to set up hoveredColor void setHoveredColor(int x); int hoveredColor() const; private: void setVisible(bool b) override; QPainterPath drawDonutPathFull(int, int, int, int); QPainterPath drawDonutPathAngle(int, int, int); QPainterPath drawRotationIndicator(qreal rotationAngle, bool canDrag); bool isPointInPixmap(QPointF&, int pos); QPainterPath createPathFromPresetIndex(int index); int numSlots(); void adjustLayout(const QPoint &p); private: int m_hoveredPreset {0}; int m_hoveredColor {0}; int m_selectedColor {0}; KisCoordinatesConverter *m_coordinatesConverter; KisViewManager *m_viewManager; KisActionManager *m_actionManager; KisFavoriteResourceManager *m_resourceManager; KisColorSelectorInterface *m_triangleColorSelector {0}; const KoColorDisplayRendererInterface *m_displayRenderer; QScopedPointer m_colorChangeCompressor; KActionCollection *m_actionCollection; - QTimer m_timer; - KisBrushHud *m_brushHud {0}; float m_popupPaletteSize {385.0}; float m_colorHistoryInnerRadius {72.0}; qreal m_colorHistoryOuterRadius {92.0}; KisRoundHudButton *m_settingsButton {0}; KisRoundHudButton *m_brushHudButton {0}; QPoint m_lastCenterPoint; QRect m_canvasRotationIndicatorRect; QRect m_resetCanvasRotationIndicatorRect; bool m_isOverCanvasRotationIndicator {false}; bool m_isRotatingCanvasIndicator {false}; bool m_isZoomingCanvas {false}; KisHighlightedToolButton *mirrorMode {0}; KisHighlightedToolButton *canvasOnlyButton {0}; QPushButton *zoomToOneHundredPercentButton {0}; QSlider *zoomCanvasSlider {0}; int zoomSliderMinValue {10}; int zoomSliderMaxValue {200}; KisAcyclicSignalConnector *m_acyclicConnector = 0; int m_cachedNumSlots {0}; qreal m_cachedRadius {0.0}; // updates the transparency and effects of the whole widget QGraphicsOpacityEffect *opacityChange {0}; KisMouseClickEater *m_clicksEater; Q_SIGNALS: void sigChangeActivePaintop(int); void sigUpdateRecentColor(int); void sigChangefGColor(const KoColor&); void sigUpdateCanvas(); void zoomLevelChanged(int); - // These are used to handle a bug: - // If pop up palette is visible and a new colour is selected, the new colour - // will be added when the user clicks on the canvas to hide the palette - // In general, we want to be able to store recent color if the pop up palette - // is not visible - void sigEnableChangeFGColor(bool); - void sigTriggerTimer(); - public Q_SLOTS: void slotUpdateIcons(); private Q_SLOTS: void slotDisplayConfigurationChanged(); void slotExternalFgColorChanged(const KoColor &color); void slotEmitColorChanged(); void slotSetSelectedColor(int x) { setSelectedColor(x); update(); } - void slotTriggerTimer(); - void slotEnableChangeFGColor(); void slotUpdate() { update(); } void slotHide() { showPopupPalette(false); } void slotShowTagsPopup(); void showHudWidget(bool visible); void slotZoomToOneHundredPercentClicked(); void slotZoomSliderChanged(int zoom); void slotZoomSliderPressed(); void slotZoomSliderReleased(); }; #endif // KIS_POPUP_PALETTE_H diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index 48211adb27..569a50dd2b 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,921 +1,930 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "opengl/kis_opengl.h" #include "opengl/kis_opengl_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #include #include "kis_assert.h" #include #include #include #ifndef GL_RENDERER # define GL_RENDERER 0x1F01 #endif using namespace KisOpenGLPrivate; namespace { // config option, set manually by main() bool g_isDebugSynchronous = false; bool g_sanityDefaultFormatIsSet = false; boost::optional openGLCheckResult; bool g_needsFenceWorkaround = false; bool g_needsPixmapCacheWorkaround = false; QString g_surfaceFormatDetectionLog; QString g_debugText("OpenGL Info\n **OpenGL not initialized**"); QVector g_openglWarningStrings; KisOpenGL::OpenGLRenderers g_supportedRenderers; KisOpenGL::OpenGLRenderer g_rendererPreferredByQt; void overrideSupportedRenderers(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt) { g_supportedRenderers = supportedRenderers; g_rendererPreferredByQt = preferredByQt; } void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) { qDebug() << "OpenGL:" << debugMessage; } KisOpenGL::OpenGLRenderer getRendererFromProbeResult(KisOpenGLModeProber::Result info) { KisOpenGL::OpenGLRenderer result = KisOpenGL::RendererDesktopGL; if (info.isOpenGLES()) { const QString rendererString = info.rendererString().toLower(); if (rendererString.contains("basic render driver") || rendererString.contains("software")) { result = KisOpenGL::RendererSoftware; } else { result = KisOpenGL::RendererOpenGLES; } } return result; } } KisOpenGLPrivate::OpenGLCheckResult::OpenGLCheckResult(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); } void KisOpenGLPrivate::appendOpenGLWarningString(KLocalizedString warning) { g_openglWarningStrings << warning; } void KisOpenGLPrivate::overrideOpenGLWarningString(QVector warnings) { g_openglWarningStrings = warnings; } void KisOpenGL::initialize() { if (openGLCheckResult) return; KIS_SAFE_ASSERT_RECOVER_NOOP(g_sanityDefaultFormatIsSet); KisOpenGL::RendererConfig config; config.format = QSurfaceFormat::defaultFormat(); openGLCheckResult = KisOpenGLModeProber::instance()->probeFormat(config, false); g_debugText.clear(); QDebug debugOut(&g_debugText); debugOut << "OpenGL Info\n"; if (openGLCheckResult) { debugOut << "\n Vendor: " << openGLCheckResult->vendorString(); debugOut << "\n Renderer: " << openGLCheckResult->rendererString(); debugOut << "\n Version: " << openGLCheckResult->driverVersionString(); debugOut << "\n Shading language: " << openGLCheckResult->shadingLanguageString(); debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); debugOut << "\n Current format: " << openGLCheckResult->format(); debugOut.nospace(); debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion(); debugOut.resetFormat(); debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions(); debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES(); } debugOut << "\n\nQPA OpenGL Detection Info"; debugOut << "\n supportsDesktopGL:" << bool(g_supportedRenderers & RendererDesktopGL); #ifdef Q_OS_WIN debugOut << "\n supportsAngleD3D11:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferAngle:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #else debugOut << "\n supportsOpenGLES:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferOpenGLES:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #endif // debugOut << "\n== log ==\n"; // debugOut.noquote(); // debugOut << g_surfaceFormatDetectionLog; // debugOut.resetFormat(); // debugOut << "\n== end log =="; dbgOpenGL.noquote().nospace() << g_debugText; KisUsageLogger::writeSysInfo(g_debugText); if (!openGLCheckResult) { return; } // Check if we have a bugged driver that needs fence workaround bool isOnX11 = false; #ifdef HAVE_X11 isOnX11 = true; #endif KisConfig cfg(true); if ((isOnX11 && openGLCheckResult->rendererString().startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) { g_needsFenceWorkaround = true; } /** * NVidia + Qt's openGL don't play well together and one cannot * draw a pixmap on a widget more than once in one rendering cycle. * * It can be workarounded by drawing strictly via QPixmapCache and * only when the pixmap size in bigger than doubled size of the * display framebuffer. That is for 8-bit HD display, you should have * a cache bigger than 16 MiB. Don't ask me why. (DK) * * See bug: https://bugs.kde.org/show_bug.cgi?id=361709 * * TODO: check if this workaround is still needed after merging * Qt5+openGL3 branch. */ if (openGLCheckResult->vendorString().toUpper().contains("NVIDIA")) { g_needsPixmapCacheWorkaround = true; const QRect screenSize = QGuiApplication::primaryScreen()->availableGeometry(); const int minCacheSize = 20 * 1024; const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize)); } } void KisOpenGL::initializeContext(QOpenGLContext *ctx) { KisConfig cfg(true); initialize(); const bool isDebugEnabled = ctx->format().testOption(QSurfaceFormat::DebugContext); dbgUI << "OpenGL: Opening new context"; if (isDebugEnabled) { // Passing ctx for ownership management only, not specifying context. // QOpenGLDebugLogger only function on the current active context. // FIXME: Do we need to make sure ctx is the active context? QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx); if (openglLogger->initialize()) { qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below."; QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged); openglLogger->startLogging(g_isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging); openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging."))); } else { qDebug() << "QOpenGLDebugLogger cannot be initialized."; delete openglLogger; } } // Double check we were given the version we requested QSurfaceFormat format = ctx->format(); QOpenGLFunctions *f = ctx->functions(); f->initializeOpenGLFunctions(); QFile log(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/krita-opengl.txt"); log.open(QFile::WriteOnly); QString vendor((const char*)f->glGetString(GL_VENDOR)); log.write(vendor.toLatin1()); log.write(", "); log.write(openGLCheckResult->rendererString().toLatin1()); log.write(", "); QString version((const char*)f->glGetString(GL_VERSION)); log.write(version.toLatin1()); log.close(); } const QString &KisOpenGL::getDebugText() { initialize(); return g_debugText; } QStringList KisOpenGL::getOpenGLWarnings() { QStringList strings; Q_FOREACH (const KLocalizedString &item, g_openglWarningStrings) { strings << item.toString(); } return strings; } // XXX Temporary function to allow LoD on OpenGL3 without triggering // all of the other 3.2 functionality, can be removed once we move to Qt5.7 bool KisOpenGL::supportsLoD() { initialize(); return openGLCheckResult && openGLCheckResult->supportsLoD(); } bool KisOpenGL::hasOpenGL3() { initialize(); return openGLCheckResult && openGLCheckResult->hasOpenGL3(); } bool KisOpenGL::hasOpenGLES() { initialize(); return openGLCheckResult && openGLCheckResult->isOpenGLES(); } bool KisOpenGL::supportsFenceSync() { initialize(); return openGLCheckResult && openGLCheckResult->supportsFenceSync(); } bool KisOpenGL::supportsRenderToFBO() { initialize(); return openGLCheckResult && openGLCheckResult->supportsFBO(); } bool KisOpenGL::needsFenceWorkaround() { initialize(); return g_needsFenceWorkaround; } bool KisOpenGL::needsPixmapCacheWorkaround() { initialize(); return g_needsPixmapCacheWorkaround; } void KisOpenGL::testingInitializeDefaultSurfaceFormat() { setDefaultSurfaceConfig(selectSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false)); } void KisOpenGL::setDebugSynchronous(bool value) { g_isDebugSynchronous = value; } KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer() { if (!openGLCheckResult) return RendererAuto; return getRendererFromProbeResult(*openGLCheckResult); } KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer() { return g_rendererPreferredByQt; } KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers() { return g_supportedRenderers; } KisOpenGL::OpenGLRenderer KisOpenGL::getUserPreferredOpenGLRendererConfig() { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return convertConfigToOpenGLRenderer(kritarc.value("OpenGLRenderer", "auto").toString()); } void KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer) { switch (renderer) { case RendererNone: return QStringLiteral("none"); case RendererSoftware: return QStringLiteral("software"); case RendererDesktopGL: return QStringLiteral("desktop"); case RendererOpenGLES: return QStringLiteral("angle"); default: return QStringLiteral("auto"); } } KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer) { if (renderer == "desktop") { return RendererDesktopGL; } else if (renderer == "angle") { return RendererOpenGLES; } else if (renderer == "software") { return RendererSoftware; } else if (renderer == "none") { return RendererNone; } else { return RendererAuto; } } KisOpenGL::OpenGLRenderer KisOpenGL::RendererConfig::rendererId() const { KisOpenGL::OpenGLRenderer result = RendererAuto; if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11Warp) { result = RendererSoftware; } else if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11) { result = RendererOpenGLES; } else if (format.renderableType() == QSurfaceFormat::OpenGL) { result = RendererDesktopGL; } else if (format.renderableType() == QSurfaceFormat::DefaultRenderableType && angleRenderer == AngleRendererD3d11) { // noop } else { qWarning() << "WARNING: unsupported combination of OpenGL renderer" << ppVar(format.renderableType()) << ppVar(angleRenderer); } return result; } namespace { typedef std::pair RendererInfo; RendererInfo getRendererInfo(KisOpenGL::OpenGLRenderer renderer) { RendererInfo info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererD3d11}; switch (renderer) { case KisOpenGL::RendererNone: info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererDefault}; break; case KisOpenGL::RendererAuto: break; case KisOpenGL::RendererDesktopGL: info = {QSurfaceFormat::OpenGL, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererOpenGLES: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererSoftware: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11Warp}; break; } return info; } KisOpenGL::RendererConfig generateSurfaceConfig(KisOpenGL::OpenGLRenderer renderer, KisConfig::RootSurfaceFormat rootSurfaceFormat, bool debugContext) { RendererInfo info = getRendererInfo(renderer); KisOpenGL::RendererConfig config; config.angleRenderer = info.second; QSurfaceFormat &format = config.format; #ifdef Q_OS_MACOS format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #elif !defined(Q_OS_ANDROID) // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); KisOpenGLModeProber::initSurfaceFormatFromConfig(rootSurfaceFormat, &format); format.setRenderableType(info.first); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing if (debugContext) { format.setOption(QSurfaceFormat::DebugContext, true); } return config; } bool isOpenGLRendererBlacklisted(const QString &rendererString, const QString &driverVersionString, QVector *warningMessage) { bool isBlacklisted = false; #ifndef Q_OS_WIN Q_UNUSED(rendererString); Q_UNUSED(driverVersionString); Q_UNUSED(warningMessage); #else // Special blacklisting of OpenGL/ANGLE is tracked on: // https://phabricator.kde.org/T7411 // HACK: Specifically detect for Intel driver build number // See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html if (rendererString.startsWith("Intel")) { KLocalizedString knownBadIntelWarning = ki18n("The Intel graphics driver in use is known to have issues with OpenGL."); KLocalizedString grossIntelWarning = ki18n( "Intel graphics drivers tend to have issues with OpenGL so ANGLE will be used by default. " "You may manually switch to OpenGL but it is not guaranteed to work properly." ); QRegularExpression regex("\\b\\d{1,2}\\.\\d{2}\\.\\d{1,3}\\.(\\d{4})\\b"); QRegularExpressionMatch match = regex.match(driverVersionString); if (match.hasMatch()) { int driverBuild = match.captured(1).toInt(); if (driverBuild > 4636 && driverBuild < 4729) { // Make ANGLE the preferred renderer for Intel driver versions // between build 4636 and 4729 (exclusive) due to an UI offset bug. // See https://communities.intel.com/thread/116003 // (Build 4636 is known to work from some test results) qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else if (driverBuild == 4358) { // There are several reports on a bug where the canvas is not being // updated properly which has debug info pointing to this build. qDebug() << "Detected Intel driver build 4358, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else { // Intel tends to randomly break OpenGL in some of their new driver // builds, therefore we just shouldn't use OpenGL by default to // reduce bug report noises. qDebug() << "Detected Intel driver, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } else { // In case Intel changed the driver version format to something that // we don't understand, we still select ANGLE. qDebug() << "Detected Intel driver with unknown version format, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } #endif return isBlacklisted; } boost::optional orderPreference(bool lhs, bool rhs) { if (lhs == rhs) return boost::none; if (lhs && !rhs) return true; if (!lhs && rhs) return false; return false; } #define ORDER_BY(lhs, rhs) if (auto res = orderPreference((lhs), (rhs))) { return *res; } class FormatPositionLess { public: FormatPositionLess() { } bool operator()(const KisOpenGL::RendererConfig &lhs, const KisOpenGL::RendererConfig &rhs) const { KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredColorSpace != KisSurfaceColorSpace::DefaultColorSpace); if (m_preferredRendererByUser != KisOpenGL::RendererSoftware) { ORDER_BY(!isFallbackOnly(lhs.rendererId()), !isFallbackOnly(rhs.rendererId())); } #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) ORDER_BY(isPreferredColorSpace(lhs.format.colorSpace()), isPreferredColorSpace(rhs.format.colorSpace())); #endif if (doPreferHDR()) { ORDER_BY(isHDRFormat(lhs.format), isHDRFormat(rhs.format)); } else { ORDER_BY(!isHDRFormat(lhs.format), !isHDRFormat(rhs.format)); } if (m_preferredRendererByUser != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByUser, rhs.rendererId() == m_preferredRendererByUser); } ORDER_BY(!isBlacklisted(lhs.rendererId()), !isBlacklisted(rhs.rendererId())); if (doPreferHDR() && m_preferredRendererByHDR != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByHDR, rhs.rendererId() == m_preferredRendererByHDR); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != KisOpenGL::RendererAuto); ORDER_BY(lhs.rendererId() == m_preferredRendererByQt, rhs.rendererId() == m_preferredRendererByQt); return false; } public: void setPreferredColorSpace(const KisSurfaceColorSpace &preferredColorSpace) { m_preferredColorSpace = preferredColorSpace; } void setPreferredRendererByQt(const KisOpenGL::OpenGLRenderer &preferredRendererByQt) { m_preferredRendererByQt = preferredRendererByQt; } void setPreferredRendererByUser(const KisOpenGL::OpenGLRenderer &preferredRendererByUser) { m_preferredRendererByUser = preferredRendererByUser; } void setPreferredRendererByHDR(const KisOpenGL::OpenGLRenderer &preferredRendererByHDR) { m_preferredRendererByHDR = preferredRendererByHDR; } void setOpenGLBlacklisted(bool openGLBlacklisted) { m_openGLBlacklisted = openGLBlacklisted; } void setOpenGLESBlacklisted(bool openGLESBlacklisted) { m_openGLESBlacklisted = openGLESBlacklisted; } bool isOpenGLBlacklisted() const { return m_openGLBlacklisted; } bool isOpenGLESBlacklisted() const { return m_openGLESBlacklisted; } KisSurfaceColorSpace preferredColorSpace() const { return m_preferredColorSpace; } KisOpenGL::OpenGLRenderer preferredRendererByUser() const { return m_preferredRendererByUser; } private: bool isHDRFormat(const QSurfaceFormat &f) const { #ifdef HAVE_HDR return f.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace || f.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace; #else Q_UNUSED(f); return false; #endif } bool isFallbackOnly(KisOpenGL::OpenGLRenderer r) const { return r == KisOpenGL::RendererSoftware; } bool isBlacklisted(KisOpenGL::OpenGLRenderer r) const { KIS_SAFE_ASSERT_RECOVER_NOOP(r == KisOpenGL::RendererAuto || r == KisOpenGL::RendererDesktopGL || r == KisOpenGL::RendererOpenGLES || r == KisOpenGL::RendererSoftware || r == KisOpenGL::RendererNone); return (r == KisOpenGL::RendererDesktopGL && m_openGLBlacklisted) || (r == KisOpenGL::RendererOpenGLES && m_openGLESBlacklisted) || (r == KisOpenGL::RendererSoftware && m_openGLESBlacklisted); } bool doPreferHDR() const { #ifdef HAVE_HDR return m_preferredColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace || m_preferredColorSpace == KisSurfaceColorSpace::scRGBColorSpace; #else return false; #endif } bool isPreferredColorSpace(const KisSurfaceColorSpace cs) const { return KisOpenGLModeProber::fuzzyCompareColorSpaces(m_preferredColorSpace, cs); return false; } private: KisSurfaceColorSpace m_preferredColorSpace = KisSurfaceColorSpace::DefaultColorSpace; KisOpenGL::OpenGLRenderer m_preferredRendererByQt = KisOpenGL::RendererDesktopGL; KisOpenGL::OpenGLRenderer m_preferredRendererByUser = KisOpenGL::RendererAuto; KisOpenGL::OpenGLRenderer m_preferredRendererByHDR = KisOpenGL::RendererAuto; bool m_openGLBlacklisted = false; bool m_openGLESBlacklisted = false; }; struct DetectionDebug : public QDebug { DetectionDebug(QString *string) : QDebug(string), m_string(string), m_originalSize(string->size()) {} ~DetectionDebug() { dbgOpenGL << m_string->right(m_string->size() - m_originalSize); *this << endl; } QString *m_string; int m_originalSize; }; } #define dbgDetection() DetectionDebug(&g_surfaceFormatDetectionLog) KisOpenGL::RendererConfig KisOpenGL::selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, bool enableDebug) { QVector warningMessages; using Info = boost::optional; QHash renderersToTest; #ifndef Q_OS_ANDROID renderersToTest.insert(RendererDesktopGL, Info()); #endif renderersToTest.insert(RendererOpenGLES, Info()); #ifdef Q_OS_WIN renderersToTest.insert(RendererSoftware, Info()); #endif #ifdef HAVE_HDR QVector formatSymbols({KisConfig::BT709_G22, KisConfig::BT709_G10, KisConfig::BT2020_PQ}); #else QVector formatSymbols({KisConfig::BT709_G22}); #endif KisOpenGL::RendererConfig defaultConfig = generateSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false); Info info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); #ifdef Q_OS_WIN if (!info) { // try software rasterizer (WARP) defaultConfig = generateSurfaceConfig(KisOpenGL::RendererSoftware, KisConfig::BT709_G22, false); info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); if (!info) { renderersToTest.remove(RendererSoftware); } } #endif if (!info) return KisOpenGL::RendererConfig(); const OpenGLRenderer defaultRenderer = getRendererFromProbeResult(*info); + /** + * On Windows we always prefer Angle, not what Qt suggests us + */ +#ifdef Q_OS_WIN + const OpenGLRenderer preferredAutoRenderer = RendererOpenGLES; +#else + const OpenGLRenderer preferredAutoRenderer = defaultRenderer; +#endif + OpenGLRenderers supportedRenderers = RendererNone; FormatPositionLess compareOp; - compareOp.setPreferredRendererByQt(defaultRenderer); + compareOp.setPreferredRendererByQt(preferredAutoRenderer); #ifdef HAVE_HDR compareOp.setPreferredColorSpace( preferredRootSurfaceFormat == KisConfig::BT709_G22 ? KisSurfaceColorSpace::sRGBColorSpace : preferredRootSurfaceFormat == KisConfig::BT709_G10 ? KisSurfaceColorSpace::scRGBColorSpace : KisSurfaceColorSpace::bt2020PQColorSpace); #else Q_UNUSED(preferredRootSurfaceFormat); compareOp.setPreferredColorSpace(KisSurfaceColorSpace::sRGBColorSpace); #endif #ifdef Q_OS_WIN compareOp.setPreferredRendererByHDR(KisOpenGL::RendererOpenGLES); #endif compareOp.setPreferredRendererByUser(preferredRenderer); compareOp.setOpenGLESBlacklisted(false); // We cannot blacklist ES drivers atm renderersToTest[defaultRenderer] = info; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { Info info = it.value(); if (!info) { info = KisOpenGLModeProber::instance()-> probeFormat(generateSurfaceConfig(it.key(), KisConfig::BT709_G22, false)); *it = info; } compareOp.setOpenGLBlacklisted( !info || isOpenGLRendererBlacklisted(info->rendererString(), info->driverVersionString(), &warningMessages)); if (info && info->isSupportedVersion()) { supportedRenderers |= it.key(); } } - OpenGLRenderer preferredByQt = defaultRenderer; + OpenGLRenderer preferredByQt = preferredAutoRenderer; if (preferredByQt == RendererDesktopGL && supportedRenderers & RendererDesktopGL && compareOp.isOpenGLBlacklisted()) { preferredByQt = RendererOpenGLES; } else if (preferredByQt == RendererOpenGLES && supportedRenderers & RendererOpenGLES && compareOp.isOpenGLESBlacklisted()) { preferredByQt = RendererDesktopGL; } QVector preferredConfigs; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { // if default mode of the renderer doesn't work, then custom won't either if (!it.value()) continue; Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) { preferredConfigs << generateSurfaceConfig(it.key(), formatSymbol, enableDebug); } } std::stable_sort(preferredConfigs.begin(), preferredConfigs.end(), compareOp); dbgDetection() << "Supported renderers:" << supportedRenderers; dbgDetection() << "Surface format preference list:"; Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { dbgDetection() << "*" << config.format; dbgDetection() << " " << config.rendererId(); } KisOpenGL::RendererConfig resultConfig = defaultConfig; if (preferredRenderer != RendererNone) { Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) dbgDetection() <<"Probing format..." << config.format.colorSpace() << config.rendererId(); #else dbgDetection() <<"Probing format..." << config.rendererId(); #endif Info info = KisOpenGLModeProber::instance()->probeFormat(config); if (info && info->isSupportedVersion()) { #ifdef Q_OS_WIN // HACK: Block ANGLE with Direct3D9 // Direct3D9 does not give OpenGL ES 3.0 // Some versions of ANGLE returns OpenGL version 3.0 incorrectly if (info->isUsingAngle() && info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) { dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened."; continue; } #endif dbgDetection() << "Found format:" << config.format; dbgDetection() << " " << config.rendererId(); resultConfig = config; break; } } { const bool colorSpaceIsCorrect = #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(), resultConfig.format.colorSpace()); #else true; #endif const bool rendererIsCorrect = compareOp.preferredRendererByUser() == KisOpenGL::RendererAuto || compareOp.preferredRendererByUser() == resultConfig.rendererId(); if (!rendererIsCorrect && colorSpaceIsCorrect) { warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected."); } else if (!colorSpaceIsCorrect) { warningMessages << ki18n("Preferred output format is not supported by available renderers"); } } } else { resultConfig.format = QSurfaceFormat(); resultConfig.angleRenderer = AngleRendererDefault; } overrideSupportedRenderers(supportedRenderers, preferredByQt); overrideOpenGLWarningString(warningMessages); return resultConfig; } void KisOpenGL::setDefaultSurfaceConfig(const KisOpenGL::RendererConfig &config) { KIS_SAFE_ASSERT_RECOVER_NOOP(!g_sanityDefaultFormatIsSet); g_sanityDefaultFormatIsSet = true; QSurfaceFormat::setDefaultFormat(config.format); #ifdef Q_OS_WIN // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", KisOpenGLModeProber::angleRendererToString(config.angleRenderer).toLatin1()); #endif if (config.format.renderableType() == QSurfaceFormat::OpenGLES) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); } else if (config.format.renderableType() == QSurfaceFormat::OpenGL) { QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); } } bool KisOpenGL::hasOpenGL() { return openGLCheckResult->isSupportedVersion(); } diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index dce0f530b5..cf48d4bc55 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,1119 +1,1119 @@ /* 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 "kis_algebra_2d.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 #include #include #include "KisOpenGLModeProber.h" #include #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) #include #endif #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; delete overlayInvertedShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; KisShaderProgram *overlayInvertedShader{0}; QScopedPointer canvasFBO; bool displayShaderCompiledWithDisplayFilterSupport{false}; 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 lineVertexBuffer; QOpenGLBuffer lineTexCoordBuffer; QVector3D vertices[6]; QVector2D texCoords[6]; #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) QOpenGLFunctions_2_1 *glFn201; #endif qreal pixelGridDrawingThreshold; bool pixelGridEnabled; QColor gridColor; QColor cursorColor; bool lodSwitchInProgress = false; 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(false); cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); connect(d->openGLImageTextures.data(), SIGNAL(sigShowFloatingMessage(QString, int, bool)), SLOT(slotShowFloatingMessage(QString, int, bool))); setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_MACOS setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, false); setAttribute(Qt::WA_DontCreateNativeAncestors, true); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // we should make sure the texture doesn't have alpha channel, // otherwise blending will not work correctly. if (KisOpenGLModeProber::instance()->useHDRMode()) { setTextureFormat(GL_RGBA16F); } else { /** * When in pure OpenGL mode, the canvas surface will have alpha * channel. Therefore, if our canvas blending algorithm produces * semi-transparent pixels (and it does), then Krita window itself * will become transparent. Which is not good. * * In Angle mode, GL_RGB8 is not available (and the transparence effect * doesn't exist at all). */ if (!KisOpenGL::hasOpenGLES()) { setTextureFormat(GL_RGB8); } } #endif setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotPixelGridModeChanged())); slotConfigChanged(); slotPixelGridModeChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } 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 (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::notifyImageColorSpaceChanged(const KoColorSpace *cs) { // FIXME: on color space change the data is refetched multiple // times by different actors! if (d->openGLImageTextures->setImageColorSpace(cs)) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } } 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(); #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) if (!KisOpenGL::hasOpenGLES()) { d->glFn201 = context()->versionFunctions(); if (!d->glFn201) { warnUI << "Cannot obtain QOpenGLFunctions_2_1, glLogicOp cannot be used"; } } else { d->glFn201 = nullptr; } #endif KisConfig cfg(true); 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); glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly d->lineVertexBuffer.create(); d->lineVertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); d->lineVertexBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); d->lineTexCoordBuffer.create(); d->lineTexCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); d->lineTexCoordBuffer.bind(); glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0 ,0); } Sync::init(context()); d->canvasInitialized = true; } /** * Loads all shaders and reports compilation problems */ void KisOpenGLCanvas2::initializeShaders() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); delete d->checkerShader; delete d->solidColorShader; delete d->overlayInvertedShader; d->checkerShader = 0; d->solidColorShader = 0; d->overlayInvertedShader = 0; try { d->checkerShader = d->shaderLoader.loadCheckerShader(); d->solidColorShader = d->shaderLoader.loadSolidColorShader(); d->overlayInvertedShader = d->shaderLoader.loadOverlayInvertedShader(); } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } initializeDisplayShader(); } void KisOpenGLCanvas2::initializeDisplayShader() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; delete d->displayShader; d->displayShader = 0; try { d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); d->displayShaderCompiledWithDisplayFilterSupport = d->displayFilter; } 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(false); qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context), QMessageBox::Close); cfg.disableOpenGL(); cfg.setCanvasState("OPENGL_FAILED"); } void KisOpenGLCanvas2::resizeGL(int width, int height) { // The given size is the widget size but here we actually want to give // KisCoordinatesConverter the viewport size aligned to device pixels. if (KisOpenGL::supportsRenderToFBO()) { - d->canvasFBO.reset(new QOpenGLFramebufferObject(QSize(width, height))); + d->canvasFBO.reset(new QOpenGLFramebufferObject(QSize(width * devicePixelRatio(), height * devicePixelRatio()))); } coordinatesConverter()->setCanvasWidgetSize(widgetSizeAlignedToDevicePixel()); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); if (d->canvasFBO) { d->canvasFBO->bind(); } renderCanvasGL(); if (d->canvasFBO) { d->canvasFBO->release(); QOpenGLFramebufferObject::blitFramebuffer(nullptr, d->canvasFBO.data(), GL_COLOR_BUFFER_BIT, GL_NEAREST); QOpenGLFramebufferObject::bindDefault(); } if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); QPainter gc(this); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { if (!d->overlayInvertedShader->bind()) { return; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection & texture matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->overlayInvertedShader->setUniformValue(d->overlayInvertedShader->location(Uniform::ModelViewProjection), modelMatrix); d->overlayInvertedShader->setUniformValue( d->overlayInvertedShader->location(Uniform::FragmentColor), QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f)); // NOTE: Texture matrix transforms flake space -> widget space -> OpenGL UV texcoord space.. const QMatrix4x4 widgetToFBOTexCoordTransform = KisAlgebra2D::mapToRectInverse(QRect(QPoint(0, this->height()), QSize(this->width(), -1 * this->height()))); const QMatrix4x4 textureMatrix = widgetToFBOTexCoordTransform * QMatrix4x4(coordinatesConverter()->flakeToWidgetTransform()); d->overlayInvertedShader->setUniformValue(d->overlayInvertedShader->location(Uniform::TextureMatrix), textureMatrix); // For the legacy shader, we should use old fixed function // blending operations if available. if (!KisOpenGL::hasOpenGL3() && !KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); #ifndef Q_OS_MACOS if (d->glFn201) { d->glFn201->glLogicOp(GL_XOR); } #else // Q_OS_MACOS glLogicOp(GL_XOR); #endif // Q_OS_MACOS #else // HAS_ONLY_OPENGL_ES KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif // HAS_ONLY_OPENGL_ES } // Paint the tool outline if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineVertexBuffer.bind(); } // Convert every disjointed subpath to a polygon and draw that polygon QList subPathPolygons = path.toSubpathPolygons(); for (int polyIndex = 0; polyIndex < subPathPolygons.size(); polyIndex++) { const QPolygonF& polygon = subPathPolygons.at(polyIndex); QVector vertices; QVector texCoords; vertices.resize(polygon.count()); texCoords.resize(polygon.count()); for (int vertIndex = 0; vertIndex < polygon.count(); vertIndex++) { QPointF point = polygon.at(vertIndex); vertices[vertIndex].setX(point.x()); vertices[vertIndex].setY(point.y()); texCoords[vertIndex].setX(point.x()); texCoords[vertIndex].setY(point.y()); } if (KisOpenGL::hasOpenGL3()) { d->lineVertexBuffer.bind(); d->lineVertexBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); d->lineTexCoordBuffer.bind(); d->lineTexCoordBuffer.allocate(texCoords.constData(), 2 * texCoords.size() * sizeof(float)); } else { d->overlayInvertedShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->overlayInvertedShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); d->overlayInvertedShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->overlayInvertedShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, texCoords.constData()); } const bool usingLegacyShader = !((KisOpenGL::hasOpenGL3() || KisOpenGL::hasOpenGLES()) && KisOpenGL::supportsRenderToFBO()); if (usingLegacyShader){ glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); } else { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->canvasFBO->texture()); glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); glBindTexture(GL_TEXTURE_2D, 0); } } if (KisOpenGL::hasOpenGL3()) { d->lineVertexBuffer.release(); d->outlineVAO.release(); } if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glDisable(GL_COLOR_LOGIC_OP); #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else { glDisable(GL_BLEND); } d->overlayInvertedShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } void KisOpenGLCanvas2::setLodResetInProgress(bool value) { d->lodSwitchInProgress = value; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height())); if (!canvas()->renderingLimit().isEmpty()) { const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect(); viewportRect &= vrect; } 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(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.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; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.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); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f)); if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineVertexBuffer.bind(); } QRectF widgetRect(0,0, widgetSize.width(), widgetSize.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->lineVertexBuffer.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->lineVertexBuffer.release(); d->outlineVAO.release(); } d->solidColorShader->release(); glDisable(GL_BLEND); } 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(); QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.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, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); const QRect renderingLimit = canvas()->renderingLimit(); if (!renderingLimit.isEmpty()) { widgetRectInImagePixels &= renderingLimit; } qreal scaleX, scaleY; converter->imagePhysicalScale(&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; QRectF modelRect; if (renderingLimit.isEmpty()) { textureRect = tile->tileRectInTexturePixels(); modelRect = tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } else { const QRect limitedTileRect = tile->tileRectInImagePixels() & renderingLimit; textureRect = tile->imageRectInTexturePixels(limitedTileRect); modelRect = limitedTileRect.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); } glActiveTexture(GL_TEXTURE0); const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } 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); glDisable(GL_BLEND); } QSize KisOpenGLCanvas2::viewportDevicePixelSize() const { // This is how QOpenGLCanvas sets the FBO and the viewport size. If // devicePixelRatioF() is non-integral, the result is truncated. int viewportWidth = static_cast(width() * devicePixelRatioF()); int viewportHeight = static_cast(height() * devicePixelRatioF()); return QSize(viewportWidth, viewportHeight); } QSizeF KisOpenGLCanvas2::widgetSizeAlignedToDevicePixel() const { QSize viewportSize = viewportDevicePixelSize(); qreal scaledWidth = viewportSize.width() / devicePixelRatioF(); qreal scaledHeight = viewportSize.height() / devicePixelRatioF(); return QSizeF(scaledWidth, scaledHeight); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg(true); 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(); d->cursorColor = cfg.getCursorMainColor(); notifyConfigChanged(); } void KisOpenGLCanvas2::slotPixelGridModeChanged() { KisConfig cfg(true); d->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold(); d->pixelGridEnabled = cfg.pixelGridEnabled(); d->gridColor = cfg.getPixelGridColor(); update(); } void KisOpenGLCanvas2::slotShowFloatingMessage(const QString &message, int timeout, bool priority) { canvas()->imageView()->showFloatingMessage(message, QIcon(), timeout, priority ? KisFloatingMessage::High : KisFloatingMessage::Medium); } 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(); const KoColorSpace *finalColorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), d->openGLImageTextures->updateInfoBuilder().destinationColorSpace()->colorDepthId().id(), d->openGLImageTextures->monitorProfile()); KoColor convertedBackgroudColor = KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8()); convertedBackgroudColor.convertTo(finalColorSpace); QVector channels = QVector(4); convertedBackgroudColor.colorSpace()->normalisedChannelsValue(convertedBackgroudColor.data(), channels); // Data returned by KoRgbU8ColorSpace comes in the order: blue, green, red. glClearColor(channels[2], channels[1], channels[0], 1.0); } glClear(GL_COLOR_BUFFER_BIT); if ((d->displayFilter && d->displayFilter->updateShader()) || (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) { KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvasInitialized); d->canvasInitialized = false; // TODO: check if actually needed? initializeDisplayShader(); d->canvasInitialized = true; } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.bind(); } drawCheckers(); drawImage(); if ((coordinatesConverter()->effectiveZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) { drawGrid(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.release(); } } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayColorConverter(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->openGLCanvasSurfaceProfile(), 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, d->openGLImageTextures->image()); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info, d->lodSwitchInProgress); } return QRect(); // FIXME: Implement dirty rect for OpenGL } QVector KisOpenGLCanvas2::updateCanvasProjection(const QVector &infoObjects) { #ifdef Q_OS_MACOS /** * On OSX openGL different (shared) contexts have different execution queues. * It means that the textures uploading and their painting can be easily reordered. * To overcome the issue, we should ensure that the textures are uploaded in the * same openGL context as the painting is done. */ QOpenGLContext *oldContext = QOpenGLContext::currentContext(); QSurface *oldSurface = oldContext ? oldContext->surface() : 0; this->makeCurrent(); #endif QVector result = KisCanvasWidgetBase::updateCanvasProjection(infoObjects); #ifdef Q_OS_MACOS if (oldContext) { oldContext->makeCurrent(oldSurface); } else { this->doneCurrent(); } #endif return result; } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp index d42043ebab..3fe531e363 100644 --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -1,446 +1,450 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_resources_snapshot.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "filter/kis_filter_configuration.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_algebra_2d.h" struct KisResourcesSnapshot::Private { Private() : currentPattern(0) , currentGradient(0) , currentGenerator(0) , compositeOp(0) { } KisImageSP image; KisDefaultBoundsBaseSP bounds; KoColor currentFgColor; KoColor currentBgColor; KoPatternSP currentPattern; KoAbstractGradientSP currentGradient; KisPaintOpPresetSP currentPaintOpPreset; KisNodeSP currentNode; qreal currentExposure; KisFilterConfigurationSP currentGenerator; QPointF axesCenter; bool mirrorMaskHorizontal = false; bool mirrorMaskVertical = false; quint8 opacity = OPACITY_OPAQUE_U8; QString compositeOpId = COMPOSITE_OVER; const KoCompositeOp *compositeOp; KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleForegroundColor; QTransform fillTransform = QTransform(); bool globalAlphaLock = false; qreal effectiveZoom = 1.0; bool presetAllowsLod = false; KisSelectionSP selectionOverride; bool hasOverrideSelection = false; }; KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; m_d->currentFgColor = resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); m_d->currentBgColor = resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value(); m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value(); /** * We should deep-copy the preset, so that long-running actions * will have correct brush parameters. Theoretically this cloning * can be expensive, but according to measurements, it takes * something like 0.1 ms for an average preset. */ KisPaintOpPresetSP p = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (p) { m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->cloneWithResourcesSnapshot(); } #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble(); m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); if (m_d->currentGenerator) { m_d->currentGenerator = m_d->currentGenerator->cloneWithResourcesSnapshot(); } QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool(); m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool(); qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble(); m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8); m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString(); setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool(); m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble(); m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::EffectiveLodAvailablility).toBool(); } KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->opacity = OPACITY_OPAQUE_U8; setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; } KisResourcesSnapshot::~KisResourcesSnapshot() { delete m_d; } void KisResourcesSnapshot::setupPainter(KisPainter* painter) { painter->setPaintColor(m_d->currentFgColor); painter->setBackgroundColor(m_d->currentBgColor); painter->setGenerator(m_d->currentGenerator); painter->setPattern(m_d->currentPattern); painter->setGradient(m_d->currentGradient); QBitArray lockflags = channelLockFlags(); if (lockflags.size() > 0) { painter->setChannelFlags(lockflags); } painter->setOpacity(m_d->opacity); painter->setCompositeOp(m_d->compositeOp); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); painter->setFillStyle(m_d->fillStyle); painter->setPatternTransform(m_d->fillTransform); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image); } void KisResourcesSnapshot::setupMaskingBrushPainter(KisPainter *painter) { KIS_SAFE_ASSERT_RECOVER_RETURN(painter->device()); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->currentPaintOpPreset->hasMaskingPreset()); painter->setPaintColor(KoColor(Qt::white, painter->device()->colorSpace())); painter->setBackgroundColor(KoColor(Qt::black, painter->device()->colorSpace())); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); // masked brush always paints in indirect mode painter->setCompositeOp(COMPOSITE_ALPHA_DARKEN); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset->createMaskingPreset(), m_d->currentNode, m_d->image); } KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const { return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0; } void KisResourcesSnapshot::setCurrentNode(KisNodeSP node) { m_d->currentNode = node; KisPaintDeviceSP device; if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) { m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId); if(!m_d->compositeOp) { m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER); } } } void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { m_d->strokeStyle = strokeStyle; } void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle) { m_d->fillStyle = fillStyle; } void KisResourcesSnapshot::setFillTransform(QTransform transform) { m_d->fillTransform = transform; } KisNodeSP KisResourcesSnapshot::currentNode() const { return m_d->currentNode; } KisImageSP KisResourcesSnapshot::image() const { return m_d->image; } bool KisResourcesSnapshot::needsIndirectPainting() const { return !m_d->currentPaintOpPreset->settings()->paintIncremental(); } QString KisResourcesSnapshot::indirectPaintingCompositeOp() const { return m_d->currentPaintOpPreset ? m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp() : COMPOSITE_ALPHA_DARKEN; } bool KisResourcesSnapshot::needsMaskingBrushRendering() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->hasMaskingPreset(); } KisSelectionSP KisResourcesSnapshot::activeSelection() const { /** * It is possible to have/use the snapshot without the image. Such * usecase is present for example in the scratchpad. */ if (m_d->hasOverrideSelection) { return m_d->selectionOverride; } KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0; KisLayerSP layer = qobject_cast(m_d->currentNode.data()); KisSelectionMaskSP mask; if((layer = qobject_cast(m_d->currentNode.data()))) { selection = layer->selection(); } else if ((mask = dynamic_cast(m_d->currentNode.data())) && mask->selection() == selection) { selection = 0; } return selection; } bool KisResourcesSnapshot::needsAirbrushing() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->isAirbrushing()); } qreal KisResourcesSnapshot::airbrushingInterval() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->airbrushInterval()); } bool KisResourcesSnapshot::needsSpacingUpdates() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->useSpacingUpdates()); } void KisResourcesSnapshot::setOpacity(qreal opacity) { m_d->opacity = opacity * OPACITY_OPAQUE_U8; } quint8 KisResourcesSnapshot::opacity() const { return m_d->opacity; } const KoCompositeOp* KisResourcesSnapshot::compositeOp() const { return m_d->compositeOp; } QString KisResourcesSnapshot::compositeOpId() const { return m_d->compositeOpId; } KoPatternSP KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; } KoColor KisResourcesSnapshot::currentFgColor() const { return m_d->currentFgColor; } KoColor KisResourcesSnapshot::currentBgColor() const { return m_d->currentBgColor; } KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const { return m_d->currentPaintOpPreset; } QTransform KisResourcesSnapshot::fillTransform() const { return m_d->fillTransform; } +KoAbstractGradientSP KisResourcesSnapshot::currentGradient() const +{ + return m_d->currentGradient; +} QBitArray KisResourcesSnapshot::channelLockFlags() const { QBitArray channelFlags; KisPaintLayer *paintLayer; if ((paintLayer = dynamic_cast(m_d->currentNode.data()))) { channelFlags = paintLayer->channelLockFlags(); if (m_d->globalAlphaLock) { if (channelFlags.isEmpty()) { channelFlags = paintLayer->colorSpace()->channelFlags(true, true); } channelFlags &= paintLayer->colorSpace()->channelFlags(true, false); } } return channelFlags; } qreal KisResourcesSnapshot::effectiveZoom() const { return m_d->effectiveZoom; } bool KisResourcesSnapshot::presetAllowsLod() const { return m_d->presetAllowsLod; } bool KisResourcesSnapshot::presetNeedsAsynchronousUpdates() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings()->needsAsynchronousUpdates(); } void KisResourcesSnapshot::setFGColorOverride(const KoColor &color) { m_d->currentFgColor = color; } void KisResourcesSnapshot::setBGColorOverride(const KoColor &color) { m_d->currentBgColor = color; } void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection) { m_d->selectionOverride = selection; m_d->hasOverrideSelection = true; // needed if selection passed is null to ignore selection } void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush) { m_d->currentPaintOpPreset = brush->cloneWithResourcesSnapshot(); #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ } diff --git a/libs/ui/tool/kis_resources_snapshot.h b/libs/ui/tool/kis_resources_snapshot.h index 8dbf87fe6a..53935f01fa 100644 --- a/libs/ui/tool/kis_resources_snapshot.h +++ b/libs/ui/tool/kis_resources_snapshot.h @@ -1,109 +1,110 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_RESOURCES_SNAPSHOT_H #define __KIS_RESOURCES_SNAPSHOT_H #include "kis_shared.h" #include "kis_shared_ptr.h" #include "kis_types.h" #include "kritaui_export.h" #include "kis_painter.h" #include "kis_default_bounds.h" class KoCanvasResourceProvider; class KoCompositeOp; class KisPainter; class KisPostExecutionUndoAdapter; class KoPattern; /** * @brief The KisResourcesSnapshot class takes a snapshot of the various resources * like colors and settings used at the begin of a stroke so subsequent * changes don't impact the running stroke. The main reason for the snapshot is that the * user can *change* the options while the stroke is being executed in the background. */ class KRITAUI_EXPORT KisResourcesSnapshot : public KisShared { public: KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisDefaultBoundsBaseSP bounds = 0); KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds = 0); ~KisResourcesSnapshot(); void setupPainter(KisPainter *painter); void setupMaskingBrushPainter(KisPainter *painter); KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const; void setCurrentNode(KisNodeSP node); void setStrokeStyle(KisPainter::StrokeStyle strokeStyle); void setFillStyle(KisPainter::FillStyle fillStyle); void setFillTransform(QTransform transform); KisNodeSP currentNode() const; KisImageSP image() const; bool needsIndirectPainting() const; QString indirectPaintingCompositeOp() const; bool needsMaskingBrushRendering() const; /** * \return currently active selection. Note that it will return * null if current node *is* the current selection. This * is done to avoid recursive selection application when * painting on selectgion masks. */ KisSelectionSP activeSelection() const; bool needsAirbrushing() const; qreal airbrushingInterval() const; bool needsSpacingUpdates() const; void setOpacity(qreal opacity); quint8 opacity() const; const KoCompositeOp* compositeOp() const; QString compositeOpId() const; KoPatternSP currentPattern() const; KoColor currentFgColor() const; KoColor currentBgColor() const; KisPaintOpPresetSP currentPaintOpPreset() const; + KoAbstractGradientSP currentGradient() const; QTransform fillTransform() const; /// @return the channel lock flags of the current node with the global override applied QBitArray channelLockFlags() const; qreal effectiveZoom() const; bool presetAllowsLod() const; bool presetNeedsAsynchronousUpdates() const; void setFGColorOverride(const KoColor &color); void setBGColorOverride(const KoColor &color); void setSelectionOverride(KisSelectionSP selection); void setBrush(const KisPaintOpPresetSP &brush); private: struct Private; Private * const m_d; }; typedef KisSharedPtr KisResourcesSnapshotSP; #endif /* __KIS_RESOURCES_SNAPSHOT_H */ diff --git a/libs/ui/widgets/kis_paintop_presets_popup.cpp b/libs/ui/widgets/kis_paintop_presets_popup.cpp index c74a69315b..54ab4a3725 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.cpp +++ b/libs/ui/widgets/kis_paintop_presets_popup.cpp @@ -1,845 +1,845 @@ /* 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 "KisResourceServerProvider.h" #include "kis_lod_availability_widget.h" #include "kis_signal_auto_connection.h" #include // ones from brush engine selector #include #include struct KisPaintOpPresetsPopup::Private { public: Ui_WdgPaintOpSettings uiWdgPaintOpPresetSettings; QGridLayout *layout; KisPaintOpConfigWidget *settingsWidget; QFont smallFont; KisCanvasResourceProvider *resourceProvider; KisFavoriteResourceManager *favoriteResManager; bool detached; bool ignoreHideEvents; bool isCreatingBrushFromScratch = false; QSize minimumSettingsWidgetSize; QRect detachedGeometry; KisSignalAutoConnectionsStore widgetConnections; }; KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, KisFavoriteResourceManager* favoriteResourceManager, KisPresetSaveWidget* savePresetWidget, QWidget * parent) : QWidget(parent) , m_d(new Private()) { setObjectName("KisPaintOpPresetsPopup"); setFont(KoDockRegistry::dockFont()); current_paintOpId = ""; m_d->resourceProvider = resourceProvider; m_d->favoriteResManager = favoriteResourceManager; m_d->uiWdgPaintOpPresetSettings.setupUi(this); m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer); m_d->layout->setSizeConstraint(QLayout::SetFixedSize); m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white); m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200)); m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setToolTip(i18n("The settings for this preset have changed from their default.")); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setToolTip(i18n("Toggle showing presets")); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setToolTip(i18n("Toggle showing scratchpad")); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setToolTip(i18n("Reload the brush preset")); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setToolTip(i18n("Rename the brush preset")); // creating a new preset from scratch. Part of the brush presets area // the menu options will get filled up later when we are generating all available paintops // in the filter drop-down newPresetBrushEnginesMenu = new QMenu(); // 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(i18nc("@title Which elements to display (e.g., thumbnails or details)", "Display")); QActionGroup *actionGroup = new QActionGroup(this); KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig(true).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->setPopupMode(QToolButton::InstantPopup); // loading preset from scratch option m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setPopupMode(QToolButton::InstantPopup); // show/hide buttons KisConfig cfg(true); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible()); if (cfg.scratchpadVisible()) { slotSwitchScratchpad(true); // show scratchpad } else { slotSwitchScratchpad(false); } m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false); slotSwitchShowPresets(false); // hide presets by default // Connections connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage())); connect(saveDialog, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(resourceSelected(KoResourceSP ))); connect (m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotRenameBrushActivated())); connect (m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton, SIGNAL(clicked(bool)), this, SLOT(slotRenameBrushDeactivated())); connect(m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton, SIGNAL(clicked(bool)), this, SLOT(slotSaveRenameCurrentBrush())); connect(m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField, SIGNAL(returnPressed()), SLOT(slotSaveRenameCurrentBrush())); connect(iconSizeSlider, SIGNAL(sliderMoved(int)), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSetIconSize(int))); connect(iconSizeSlider, SIGNAL(sliderReleased()), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSaveIconSize())); connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchScratchpad(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault())); connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillLayer())); connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient())); connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground())); m_d->settingsWidget = 0; setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); connect(m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton, SIGNAL(clicked()), this, SLOT(slotSaveBrushPreset())); connect(m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton, SIGNAL(clicked()), this, SLOT(slotSaveNewBrushPreset())); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), this, SIGNAL(reloadPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(dirtyPresetToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushSizeToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushOpacityToggled(bool))); // preset widget connections connect(m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SIGNAL(signalResourceSelected(KoResourceSP ))); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), SLOT(slotUpdatePresetSettings())); m_d->detached = false; m_d->ignoreHideEvents = false; m_d->minimumSettingsWidgetSize = QSize(0, 0); m_d->detachedGeometry = QRect(100, 100, 0, 0); m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets()); m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize()); m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity()); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setCanvasResourceManager(resourceProvider->resourceManager()); connect(resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(slotResourceChanged(int,QVariant))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodAvailability(bool)), SLOT(slotLodAvailabilityChanged(bool))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodThreshold(qreal)), SLOT(slotLodThresholdChanged(qreal))); slotResourceChanged(KisCanvasResourceProvider::LodAvailability, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodAvailability)); slotResourceChanged(KisCanvasResourceProvider::LodSizeThreshold, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodSizeThreshold)); connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePaintOpFilter())); connect(m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset, SIGNAL(clicked()), this, SLOT(slotBlackListCurrentPreset())); updateThemedIcons(); // setup things like the scene construct images, layers, etc that is a one-time thing - m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setup(); + m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setup(resourceProvider->resourceManager()); } void KisPaintOpPresetsPopup::slotBlackListCurrentPreset() { KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP curPreset = m_d->resourceProvider->currentPreset(); if (rServer->resourceByName(curPreset->name())) { rServer->removeResourceFromServer(curPreset); } } void KisPaintOpPresetsPopup::slotRenameBrushActivated() { toggleBrushRenameUIActive(true); } void KisPaintOpPresetsPopup::slotRenameBrushDeactivated() { toggleBrushRenameUIActive(false); } void KisPaintOpPresetsPopup::toggleBrushRenameUIActive(bool isRenaming) { // This function doesn't really do anything except get the UI in a state to rename a brush preset m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setVisible(isRenaming); m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton->setVisible(isRenaming); m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton->setVisible(isRenaming); // hide these below areas while renaming m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setEnabled(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setVisible(!isRenaming); // if the presets area is shown, only then can you show/hide the load default brush // need to think about weird state when you are in the middle of renaming a brush // what happens if you try to change presets. maybe we should auto-hide (or disable) // the presets area in this case if (m_d->uiWdgPaintOpPresetSettings.presetWidget->isVisible()) { m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setVisible(!isRenaming); } } void KisPaintOpPresetsPopup::slotSaveRenameCurrentBrush() { // if you are renaming a brush, that is different than updating the settings // make sure we are in a clean state before renaming. This logic might change, // but that is what we are going with for now emit reloadPresetClicked(); // 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 originalPresetName = curPreset->name(); QString renamedPresetName = m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->text(); QString renamedPresetPathAndFile = renamedPresetName + curPreset->defaultFileExtension(); // create a new brush preset with the name specified and add to resource provider KisPaintOpPresetSP newPreset = curPreset->clone().dynamicCast(); 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->setDirty(false); newPreset->setValid(true); rServer->addResource(newPreset); resourceSelected(newPreset); // refresh and select our freshly renamed resource // Now blacklist the original file if (rServer->resourceByName(originalPresetName)) { rServer->removeResourceFromServer(curPreset); } m_d->favoriteResManager->updateFavoritePresets(); 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()); } else if (key == KisCanvasResourceProvider::LodSizeThreshold) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodThreshold(value.toDouble()); } else if (key == KisCanvasResourceProvider::Size) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedSize(value.toDouble()); } } void KisPaintOpPresetsPopup::slotLodAvailabilityChanged(bool value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodAvailability, QVariant(value)); } void KisPaintOpPresetsPopup::slotLodThresholdChanged(qreal value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodSizeThreshold, 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; delete newPresetBrushEnginesMenu; } void KisPaintOpPresetsPopup::setPaintOpSettingsWidget(QWidget * widget) { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry(); } m_d->layout->update(); updateGeometry(); m_d->widgetConnections.clear(); m_d->settingsWidget = 0; if (widget) { m_d->settingsWidget = dynamic_cast(widget); KIS_ASSERT_RECOVER_RETURN(m_d->settingsWidget); KisConfig cfg(true); if (m_d->settingsWidget->supportScratchBox() && cfg.scratchpadVisible()) { slotSwitchScratchpad(true); } else { slotSwitchScratchpad(false); } m_d->widgetConnections.addConnection(m_d->settingsWidget, SIGNAL(sigConfigurationItemChanged()), this, SLOT(slotUpdateLodAvailability())); widget->setFont(m_d->smallFont); QSize hint = widget->sizeHint(); m_d->minimumSettingsWidgetSize = QSize(qMax(hint.width(), m_d->minimumSettingsWidgetSize.width()), qMax(hint.height(), m_d->minimumSettingsWidgetSize.height())); widget->setMinimumSize(m_d->minimumSettingsWidgetSize); m_d->layout->addWidget(widget); // hook up connections that will monitor if our preset is dirty or not. Show a notification if it is if (m_d->resourceProvider && m_d->resourceProvider->currentPreset() ) { KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset(); m_d->widgetConnections.addConnection(preset->updateProxy(), SIGNAL(sigSettingsChanged()), this, SLOT(slotUpdatePresetSettings())); } m_d->layout->update(); widget->show(); } slotUpdateLodAvailability(); } void KisPaintOpPresetsPopup::slotUpdateLodAvailability() { if (!m_d->settingsWidget) return; KisPaintopLodLimitations l = m_d->settingsWidget->lodLimitations(); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLimitations(l); } QImage KisPaintOpPresetsPopup::cutOutOverlay() { return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay(); } void KisPaintOpPresetsPopup::contextMenuEvent(QContextMenuEvent *e) { Q_UNUSED(e); } void KisPaintOpPresetsPopup::switchDetached(bool show) { if (parentWidget()) { m_d->detached = !m_d->detached; if (m_d->detached) { m_d->ignoreHideEvents = true; if (show) { parentWidget()->show(); } m_d->ignoreHideEvents = false; } else { parentWidget()->hide(); } KisConfig cfg(false); cfg.setPaintopPopupDetached(m_d->detached); } } void KisPaintOpPresetsPopup::setCreatingBrushFromScratch(bool enabled) { m_d->isCreatingBrushFromScratch = enabled; } void KisPaintOpPresetsPopup::resourceSelected(KoResourceSP resource) { // this gets called every time the brush editor window is opened // TODO: this gets called multiple times whenever the preset is changed in the presets area // the connections probably need to be thought about with this a bit more to keep things in sync m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(resource); // find the display name of the brush engine and append it to the selected preset display QString currentBrushEngineName; QPixmap currentBrushEngineIcon = QPixmap(26, 26); currentBrushEngineIcon.fill(Qt::transparent); for(int i=0; i < sortedBrushEnginesList.length(); i++) { if (sortedBrushEnginesList.at(i).id == currentPaintOpId() ) { currentBrushEngineName = sortedBrushEnginesList.at(i).name; currentBrushEngineIcon = sortedBrushEnginesList.at(i).icon.pixmap(26, 26); } } // brush names have underscores as part of the file name (to help with building). We don't really need underscores // when viewing the names, so replace them with spaces QString formattedBrushName = resource->name().replace("_", " "); m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(formattedBrushName); m_d->uiWdgPaintOpPresetSettings.currentBrushEngineLabel->setText(i18nc("%1 is the name of a brush engine", "%1 Engine", currentBrushEngineName)); m_d->uiWdgPaintOpPresetSettings.currentBrushEngineIcon->setPixmap(currentBrushEngineIcon); m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setText(resource->name()); // use file name // get the preset image and pop it into the thumbnail area on the top of the brush editor m_d->uiWdgPaintOpPresetSettings.presetThumbnailicon->setPixmap(QPixmap::fromImage(resource->image().scaled(55, 55, Qt::KeepAspectRatio, Qt::SmoothTransformation))); 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++) { KisPaintOpInfo paintOpInfo; paintOpInfo.id = list.at(i)->id(); paintOpInfo.name = list.at(i)->name(); paintOpInfo.icon = list.at(i)->icon(); 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(Qt::transparent); // if we create a new brush from scratch, we need a full list of paintops to choose from // we don't want "All", so populate the list before that is added newPresetBrushEnginesMenu->actions().clear(); // clean out list in case we run this again newBrushEngineOptions.clear(); for (int j = 0; j < sortedBrushEnginesList.length(); j++) { KisAction * newEngineAction = static_cast( newPresetBrushEnginesMenu->addAction(sortedBrushEnginesList[j].name)); newEngineAction->setObjectName(sortedBrushEnginesList[j].id); // we need the ID for changing the paintop when action triggered newEngineAction->setIcon(sortedBrushEnginesList[j].icon); newBrushEngineOptions.append(newEngineAction); connect(newEngineAction, SIGNAL(triggered()), this, SLOT(slotCreateNewBrushPresetEngine())); } m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setMenu(newPresetBrushEnginesMenu); // fill the list into the brush combo box sortedBrushEnginesList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), QIcon(emptyPixmap), 0 )); 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); if (parentWidget()) { // Make sure resizing doesn't push this widget out of the screen QRect screenRect = QApplication::desktop()->availableGeometry(this); QRect newPositionRect = kisEnsureInRect(parentWidget()->geometry(), screenRect); parentWidget()->setGeometry(newPositionRect); } } bool KisPaintOpPresetsPopup::detached() const { return m_d->detached; } void KisPaintOpPresetsPopup::slotSwitchScratchpad(bool visible) { // hide all the internal controls except the toggle button m_d->uiWdgPaintOpPresetSettings.scratchPad->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillGradient->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillLayer->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillSolid->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.scratchpadSidebarLabel->setVisible(visible); if (visible) { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } else { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } KisConfig cfg(false); cfg.setScratchpadVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowEditor(bool visible) { m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowPresets(bool visible) { m_d->uiWdgPaintOpPresetSettings.presetWidget->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.engineFilterLabel->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.presetsSidebarLabel->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setVisible(visible); // we only want a spacer to work when the toggle icon is present. Otherwise the list of presets will shrink // which is something we don't want if (visible) { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::Ignored,QSizePolicy::Ignored); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } else { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } } void KisPaintOpPresetsPopup::slotUpdatePaintOpFilter() { QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data QString filterPaintOpId = userData.toString(); if (filterPaintOpId == "all_options") { filterPaintOpId = ""; } m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(filterPaintOpId); } void KisPaintOpPresetsPopup::slotSaveBrushPreset() { // here we are assuming that people want to keep their existing preset icon. We will just update the // settings and save a new copy with the same name. // there is a dialog with save options, but we don't need to show it in this situation saveDialog->useNewBrushDialog(false); // this mostly just makes sure we keep the existing brush preset name when saving saveDialog->loadExistingThumbnail(); // This makes sure we use the existing preset icon when updating the existing brush preset saveDialog->savePreset(); // refresh the view settings so the brush doesn't appear dirty slotUpdatePresetSettings(); } void KisPaintOpPresetsPopup::slotSaveNewBrushPreset() { saveDialog->useNewBrushDialog(true); saveDialog->saveScratchPadThumbnailArea(m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay()); saveDialog->showDialog(); } void KisPaintOpPresetsPopup::slotCreateNewBrushPresetEngine() { emit createPresetFromScratch(sender()->objectName()); } void KisPaintOpPresetsPopup::updateViewSettings() { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->updateViewSettings(); } void KisPaintOpPresetsPopup::currentPresetChanged(KisPaintOpPresetSP preset) { if (preset) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(preset); setCurrentPaintOpId(preset->paintOp().id()); } } void KisPaintOpPresetsPopup::updateThemedIcons() { 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.newPresetEngineButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setIcon(KisIconUtils::loadIcon("deletelayer")); 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.newPresetEngineButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setIcon(KisIconUtils::loadIcon("deletelayer")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("configure")); // if we cannot see the "Preset label", we know it is not visible // maybe this can also be stored in the config like the scratchpad? if (m_d->uiWdgPaintOpPresetSettings.presetsSidebarLabel->isVisible()) { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::Ignored,QSizePolicy::Ignored); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } else { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } // we store whether the scratchpad if visible in the config. KisConfig cfg(true); if (cfg.scratchpadVisible()) { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } else { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } } void KisPaintOpPresetsPopup::slotUpdatePresetSettings() { if (!m_d->resourceProvider) { return; } if (!m_d->resourceProvider->currentPreset()) { return; } // hide options on UI if we are creating a brush preset from scratch to prevent confusion if (m_d->isCreatingBrushFromScratch) { m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setVisible(false); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(false); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setVisible(false); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(false); } else { bool isPresetDirty = m_d->resourceProvider->currentPreset()->isDirty(); // 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.renameBrushPresetButton->setVisible(true); } // update live preview area in here... // don't update the live preview if the widget is not visible. if (m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->isVisible()) { m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setCurrentPreset(m_d->resourceProvider->currentPreset()); m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->requestUpdateStroke(); } } diff --git a/libs/ui/widgets/kis_preset_live_preview_view.cpp b/libs/ui/widgets/kis_preset_live_preview_view.cpp index 9e08e804bc..56586d4bf2 100644 --- a/libs/ui/widgets/kis_preset_live_preview_view.cpp +++ b/libs/ui/widgets/kis_preset_live_preview_view.cpp @@ -1,391 +1,384 @@ /* * Copyright (c) 2017 Scott Petrovic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "kis_paintop_settings.h" #include #include #include "KisAsyncronousStrokeUpdateHelper.h" #include #include #include "kis_transaction.h" +#include KisPresetLivePreviewView::KisPresetLivePreviewView(QWidget *parent) : QGraphicsView(parent), m_updateCompressor(100, KisSignalCompressor::FIRST_ACTIVE) { connect(&m_updateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); } KisPresetLivePreviewView::~KisPresetLivePreviewView() { delete m_noPreviewText; delete m_brushPreviewScene; } -void KisPresetLivePreviewView::setup() +void KisPresetLivePreviewView::setup(KoCanvasResourceProvider* resourceManager) { + m_resourceManager = resourceManager; + // initializing to 0 helps check later if they actually have something in them m_noPreviewText = 0; m_sceneImageItem = 0; setHorizontalScrollBarPolicy ( Qt::ScrollBarAlwaysOff ); setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff ); // layer image needs to be big enough to get an entire stroke of data m_canvasSize.setWidth(this->width()); m_canvasSize.setHeight(this->height()); m_canvasCenterPoint.setX(m_canvasSize.width()*0.5); m_canvasCenterPoint.setY(m_canvasSize.height()*0.5); m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); m_image = new KisImage(0, m_canvasSize.width(), m_canvasSize.height(), m_colorSpace, "stroke sample image"); m_layer = new KisPaintLayer(m_image, "livePreviewStrokeSample", OPACITY_OPAQUE_U8, m_colorSpace); // set scene for the view m_brushPreviewScene = new QGraphicsScene(); setScene(m_brushPreviewScene); } void KisPresetLivePreviewView::setCurrentPreset(KisPaintOpPresetSP preset) { m_currentPreset = preset; } void KisPresetLivePreviewView::requestUpdateStroke() { m_updateCompressor.start(); } void KisPresetLivePreviewView::updateStroke() { // do not paint a stroke if we are any of these engines (they have some issue currently) if (m_currentPreset->paintOp().id() == "roundmarker" || m_currentPreset->paintOp().id() == "experimentbrush" || m_currentPreset->paintOp().id() == "duplicate") { paintBackground(); slotPreviewGenerationCompleted(); return; } if (!m_previewGenerationInProgress) { paintBackground(); setupAndPaintStroke(); } else { m_updateCompressor.start(); } } void KisPresetLivePreviewView::slotPreviewGenerationCompleted() { m_previewGenerationInProgress = false; QImage m_temp_image; m_temp_image = m_layer->paintDevice()->convertToQImage(0, m_image->bounds()); // only add the object once...then just update the pixmap so we can move the preview around if (!m_sceneImageItem) { m_sceneImageItem = m_brushPreviewScene->addPixmap(QPixmap::fromImage(m_temp_image)); } else { m_sceneImageItem->setPixmap(QPixmap::fromImage(m_temp_image)); } } void KisPresetLivePreviewView::paintBackground() { // clean up "no preview" text object if it exists. we will add it later if we need it if (m_noPreviewText) { this->scene()->removeItem(m_noPreviewText); m_noPreviewText = 0; } if (m_currentPreset->paintOp().id() == "colorsmudge" || m_currentPreset->paintOp().id() == "deformbrush" || m_currentPreset->paintOp().id() == "filter") { // easier to see deformations and smudging with alternating stripes in the background // paint the whole background with alternating stripes // filter engine may or may not show things depending on the filter...but it is better than nothing int grayStrips = 20; for (int i=0; i < grayStrips; i++ ) { float sectionPercent = 1.0 / (float)grayStrips; bool isAlternating = i % 2; KoColor fillColor(m_layer->paintDevice()->colorSpace()); if (isAlternating) { fillColor.fromQColor(QColor(80,80,80)); } else { fillColor.fromQColor(QColor(140,140,140)); } const QRect fillRect(m_layer->image()->width()*sectionPercent*i, 0, m_layer->image()->width()*(sectionPercent*i +sectionPercent), m_layer->image()->height()); KisTransaction t(m_layer->paintDevice()); m_layer->paintDevice()->fill(fillRect, fillColor); t.end(); } m_paintColor = KoColor(Qt::white, m_colorSpace); } else if (m_currentPreset->paintOp().id() == "roundmarker" || m_currentPreset->paintOp().id() == "experimentbrush" || m_currentPreset->paintOp().id() == "duplicate" ) { // cases where we will not show a preview for now // roundbrush (quick) -- this isn't showing anything, disable showing preview // experimentbrush -- this creates artifacts that carry over to other previews and messes up their display // duplicate (clone) brush doesn't have a preview as it doesn't show anything) if(m_sceneImageItem) { this->scene()->removeItem(m_sceneImageItem); m_sceneImageItem = 0; } QFont font; font.setPixelSize(14); font.setBold(false); m_noPreviewText = this->scene()->addText(i18n("No Preview for this engine"),font); m_noPreviewText->setPos(50, this->height()/4); return; } else { // fill with gray first to clear out what existed from previous preview KisTransaction t(m_layer->paintDevice()); m_layer->paintDevice()->fill(m_image->bounds(), KoColor(palette().color(QPalette::Background) , m_colorSpace)); t.end(); m_paintColor = KoColor(palette().color(QPalette::Text), m_colorSpace); } } class NotificationStroke : public QObject, public KisSimpleStrokeStrategy { Q_OBJECT public: NotificationStroke() : KisSimpleStrokeStrategy(QLatin1String("NotificationStroke")) { setClearsRedoOnStart(false); this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER); this->enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER); } void initStrokeCallback() override { emit timeout(); } void cancelStrokeCallback() override { emit cancelled(); } Q_SIGNALS: void timeout(); void cancelled(); }; void KisPresetLivePreviewView::setupAndPaintStroke() { // limit the brush stroke size. larger brush strokes just don't look good and are CPU intensive // we are making a proxy preset and setting it to the painter...otherwise setting the brush size of the original preset // will fire off signals that make this run in an infinite loop qreal previewSize = qBound(3.0, m_currentPreset->settings()->paintOpSize(), 25.0 ); // constrain live preview brush size //Except for the sketchbrush where it determine sthe history. if (m_currentPreset->paintOp().id() == "sketchbrush" || m_currentPreset->paintOp().id() == "spraybrush") { previewSize = qMax(3.0, m_currentPreset->settings()->paintOpSize()); } KisPaintOpPresetSP proxy_preset = m_currentPreset->clone().dynamicCast(); KisPaintOpSettingsSP settings = proxy_preset->settings(); settings->setPaintOpSize(previewSize); int maxTextureSize = 200; int textureOffsetX = settings->getInt("Texture/Pattern/MaximumOffsetX")*2; int textureOffsetY = settings->getInt("Texture/Pattern/MaximumOffsetY")*2; double textureScale = settings->getDouble("Texture/Pattern/Scale"); if ( textureOffsetX*textureScale> maxTextureSize || textureOffsetY*textureScale > maxTextureSize) { int maxSize = qMax(textureOffsetX, textureOffsetY); double result = qreal(maxTextureSize) / maxSize; settings->setProperty("Texture/Pattern/Scale", result); } if (proxy_preset->paintOp().id() == "spraybrush") { QDomElement element; QDomDocument d; QString brushDefinition = settings->getString("brush_definition"); if (!brushDefinition.isEmpty()) { d.setContent(brushDefinition, false); element = d.firstChildElement("Brush"); KisBrushSP brush = KisBrush::fromXML(element, KisGlobalResourcesInterface::instance()); qreal width = brush->image().width(); qreal scale = brush->scale(); qreal diameterToBrushRatio = 1.0; qreal diameter = settings->getInt("Spray/diameter"); //hack, 1000 being the maximum possible brushsize. if (brush->filename().endsWith(".svg")) { diameterToBrushRatio = diameter/(1000.0*scale); scale = 25.0 / 1000.0; } else { if (width * scale > 25.0) { diameterToBrushRatio = diameter / (width * scale); scale = 25.0 / width; } } settings->setProperty("Spray/diameter", int(25.0 * diameterToBrushRatio)); brush->setScale(scale); d.clear(); element = d.createElement("Brush"); brush->toXML(d, element); d.appendChild(element); settings->setProperty("brush_definition", d.toString()); } } - // Preset preview cannot display gradient color source: there is - // no resource manager for KisResourcesSnapshot, therefore gradient is nullptr. - // BUG: 385521 (Selecting "Gradient" in brush editor crashes krita) - if (proxy_preset->paintOp().id() == "paintbrush") { - QString colorSourceType = settings->getString("ColorSource/Type", "plain"); - if (colorSourceType == "gradient") { - settings->setProperty("ColorSource/Type", "plain"); - } - } - proxy_preset->setSettings(settings); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(m_image, - m_layer); + m_layer, m_resourceManager); resources->setOpacity(settings->paintOpOpacity()); resources->setBrush(proxy_preset); resources->setFGColorOverride(m_paintColor); KisFreehandStrokeInfo *strokeInfo = new KisFreehandStrokeInfo(); KisStrokeStrategy *stroke = new FreehandStrokeStrategy(resources, strokeInfo, kundo2_noi18n("temp_stroke")); KisStrokeId strokeId = m_image->startStroke(stroke); // paint the stroke. The sketchbrush gets a different shape than the others to show how it works if (proxy_preset->paintOp().id() == "sketchbrush" || proxy_preset->paintOp().id() == "curvebrush" || proxy_preset->paintOp().id() == "particlebrush") { qreal startX = m_canvasCenterPoint.x() - (this->width()*0.4); qreal endX = m_canvasCenterPoint.x() + (this->width()*0.4); qreal middle = m_canvasCenterPoint.y(); KisPaintInformation pointOne; pointOne.setPressure(0.0); pointOne.setPos(QPointF(startX, middle)); KisPaintInformation pointTwo; pointTwo.setPressure(0.0); pointTwo.setPos(QPointF(startX, middle)); int repeats = 8; for (int i = 0; i < repeats; i++) { pointOne.setPos(pointTwo.pos()); pointOne.setPressure(pointTwo.pressure()); pointTwo.setPressure((1.0/repeats)*(i+1)); qreal xPos = ((1.0/repeats) * (i+1) * (endX-startX) )+startX; pointTwo.setPos(QPointF(xPos, middle)); qreal offset = (this->height()/(repeats*1.5))*(i+1); qreal handleY = middle + offset; if (i%2 == 0) { handleY = middle - offset; } m_image->addJob(strokeId, new FreehandStrokeStrategy::Data(0, pointOne, QPointF(pointOne.pos().x(), handleY), QPointF(pointTwo.pos().x(), handleY), pointTwo)); m_image->addJob(strokeId, new KisAsyncronousStrokeUpdateHelper::UpdateData(true)); } } else { // paint an S curve m_curvePointPI1.setPos(QPointF(m_canvasCenterPoint.x() - (this->width()*0.45), m_canvasCenterPoint.y() + (this->height()*0.2))); m_curvePointPI1.setPressure(0.0); m_curvePointPI2.setPos(QPointF(m_canvasCenterPoint.x() + (this->width()*0.4), m_canvasCenterPoint.y() - (this->height()*0.2) )); m_curvePointPI2.setPressure(1.0); m_image->addJob(strokeId, new FreehandStrokeStrategy::Data(0, m_curvePointPI1, QPointF(m_canvasCenterPoint.x(), m_canvasCenterPoint.y()-this->height()), QPointF(m_canvasCenterPoint.x(), m_canvasCenterPoint.y()+this->height()), m_curvePointPI2)); m_image->addJob(strokeId, new KisAsyncronousStrokeUpdateHelper::UpdateData(true)); } m_image->endStroke(strokeId); m_previewGenerationInProgress = true; NotificationStroke *notificationStroke = new NotificationStroke(); connect(notificationStroke, SIGNAL(timeout()), SLOT(slotPreviewGenerationCompleted())); KisStrokeId notificationId = m_image->startStroke(notificationStroke); m_image->endStroke(notificationId); // TODO: if we don't have any regressions because of it until 4.2.8, then // just remove this code. // even though the brush is cloned, the proxy_preset still has some connection to the original preset which will mess brush sizing // we need to return brush size to normal.The normal brush sends out a lot of extra signals, so keeping the proxy for now //proxy_preset->settings()->setPaintOpSize(originalPresetSize); } #include "kis_preset_live_preview_view.moc" diff --git a/libs/ui/widgets/kis_preset_live_preview_view.h b/libs/ui/widgets/kis_preset_live_preview_view.h index abace25032..0e42323465 100644 --- a/libs/ui/widgets/kis_preset_live_preview_view.h +++ b/libs/ui/widgets/kis_preset_live_preview_view.h @@ -1,157 +1,162 @@ /* * Copyright (c) 2017 Scott Petrovic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_PRESET_LIVE_PREVIEW_ #define _KIS_PRESET_LIVE_PREVIEW_ #include #include #include #include #include #include "kis_paintop_preset.h" #include "KoColorSpaceRegistry.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_distance_information.h" #include "kis_painting_information_builder.h" #include #include #include #include "kis_signal_compressor.h" +class KoCanvasResourceProvider; + /** * Widget for displaying a live brush preview of your * selected brush. It listens for signalsetting changes * that the brush preset outputs and updates the preview * accordingly. This class can be added to a UI file * similar to how a QGraphicsView is added */ class KisPresetLivePreviewView : public QGraphicsView { Q_OBJECT public: KisPresetLivePreviewView(QWidget *parent); ~KisPresetLivePreviewView(); /** * @brief one time setup for initialization of many variables. * This live preview might be in a UI file, so make sure to * call this before starting to use it */ - void setup(); + void setup(KoCanvasResourceProvider* resourceManager); /** * @brief set the current preset from resource manager for the live preview to use. * Good to call this every stroke update in case the preset has changed * @param preset the current preset from the resource manager */ void setCurrentPreset(KisPaintOpPresetSP preset); void requestUpdateStroke(); private Q_SLOTS: void updateStroke(); void slotPreviewGenerationCompleted(); private: + ///internally sets the Resource Provider for brush preview (allowing gradients in preview) + KoCanvasResourceProvider* m_resourceManager; + /// internally sets the image area for brush preview KisImageSP m_image; /// internally sets the layer area for brush preview KisLayerSP m_layer; /// internally sets the color space for brush preview const KoColorSpace *m_colorSpace; /// the color which is used for rendering the stroke KoColor m_paintColor; /// the scene that can add items like text and the brush stroke image QGraphicsScene *m_brushPreviewScene; /// holds the preview brush stroke data QGraphicsPixmapItem *m_sceneImageItem; /// holds the 'no preview available' text object QGraphicsTextItem *m_noPreviewText; /// holds the width and height of the image of the brush preview /// Probably can later add set functions to make this customizable /// It is hard-coded to 1200 x 400 for right now for image size QRect m_canvasSize; /// convenience variable used internally when positioning the objects /// and points in the scene QPointF m_canvasCenterPoint; /// internal variables for constructing the stroke start and end shape /// there are two points that construct the "S" curve with this KisDistanceInformation m_currentDistance; QPainterPath m_curvedLine; KisPaintInformation m_curvePointPI1; KisPaintInformation m_curvePointPI2; /// internally stores the current preset. /// See setCurrentPreset(KisPaintOpPresetSP preset) /// for setting this externally KisPaintOpPresetSP m_currentPreset; /// holds the current zoom(scale) level of scene float m_scaleFactor; /// internal reference for internal brush size /// used to check if our brush size has changed /// do zooming and other things internally if it has changed float m_currentBrushSize = 1.0; bool m_previewGenerationInProgress = false; KisSignalCompressor m_updateCompressor; /// the range of brush sizes that will control zooming in/out const float m_minBrushVal = 10.0; const float m_maxBrushVal = 100.0; /// range of scale values. 1.0 == 100% const qreal m_minScale = 1.0; const qreal m_maxScale = 0.3; /// multiplier that is used for lengthening the brush stroke points const float m_minStrokeScale = 0.4; // for smaller brush stroke const float m_maxStrokeScale = 1.0; // for larger brush stroke /** * @brief works as both clearing the previous stroke, providing * striped backgrounds for smudging brushes, and text if there is no preview */ void paintBackground(); /** * @brief creates and performs the actual stroke that goes on top of the background * this is internally and should always be called after the paintBackground() */ void setupAndPaintStroke(); }; #endif diff --git a/libs/ui/widgets/kis_transport_controls.cpp b/libs/ui/widgets/kis_transport_controls.cpp new file mode 100644 index 0000000000..f1387365e3 --- /dev/null +++ b/libs/ui/widgets/kis_transport_controls.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020 Emmet O'Neill + * Copyright (c) 2020 Eoin O'Neill + * + * 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_transport_controls.h" + +#include +#include + +#include "kis_icon.h" +#include "klocalizedstring.h" + +KisTransportControls::KisTransportControls(QWidget* parent) + : QWidget(parent) +{ + QHBoxLayout* layout = new QHBoxLayout(parent); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(0); + + buttonBack = new QPushButton(KisIconUtils::loadIcon("prevframe"), "", this); + buttonBack->setToolTip(i18n("Back")); + layout->addWidget(buttonBack); + connect(buttonBack, SIGNAL(released()), this, SIGNAL(back())); + + buttonStop = new QPushButton(KisIconUtils::loadIcon("animation_stop"), "", this); + buttonStop->setToolTip(i18n("Stop")); + layout->addWidget(buttonStop); + connect(buttonStop, SIGNAL(released()), this, SIGNAL(stop())); + + buttonPlayPause = new QPushButton(KisIconUtils::loadIcon("animation_play"), "", this); + buttonPlayPause->setToolTip(i18n("Play/Pause")); + layout->addWidget(buttonPlayPause); + connect(buttonPlayPause, SIGNAL(released()), this, SIGNAL(playPause())); + + buttonForward = new QPushButton(KisIconUtils::loadIcon("nextframe"), "", this); + buttonForward->setToolTip(i18n("Forward")); + layout->addWidget(buttonForward); + connect(buttonForward, SIGNAL(released()), this, SIGNAL(forward())); + + setLayout(layout); +} + +KisTransportControls::~KisTransportControls() +{ +} + +QSize KisTransportControls::sizeHint() const +{ + return QSize(32, 32); +} + +void KisTransportControls::setPlaying(bool playing) +{ + if (playing) { + buttonPlayPause->setIcon(KisIconUtils::loadIcon("animation_pause")); + } else { + buttonPlayPause->setIcon(KisIconUtils::loadIcon("animation_play")); + } +} diff --git a/plugins/dockers/animation/timeline_docker.h b/libs/ui/widgets/kis_transport_controls.h similarity index 52% copy from plugins/dockers/animation/timeline_docker.h copy to libs/ui/widgets/kis_transport_controls.h index d9a2261d7c..2ada10cec6 100644 --- a/plugins/dockers/animation/timeline_docker.h +++ b/libs/ui/widgets/kis_transport_controls.h @@ -1,54 +1,53 @@ /* - * Copyright (c) 2015 Jouni Pentikäinen + * Copyright (c) 2020 Emmet O'Neill + * Copyright (c) 2020 Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _TIMELINE_DOCKER_H_ -#define _TIMELINE_DOCKER_H_ +#ifndef KISTRANSPORTCONTROLS_H +#define KISTRANSPORTCONTROLS_H -#include "kritaimage_export.h" +#include -#include -#include +#include "kritaui_export.h" -#include - -class KisCanvas2; -class KisAction; - -class TimelineDocker : public QDockWidget, public KisMainwindowObserver +class KRITAUI_EXPORT KisTransportControls : public QWidget { Q_OBJECT + public: - TimelineDocker(); - ~TimelineDocker() override; + KisTransportControls(QWidget* parent = nullptr); + ~KisTransportControls(); - QString observerName() override { return "TimelineDocker"; } - void setCanvas(KoCanvasBase *canvas) override; - void unsetCanvas() override; - void setViewManager(KisViewManager *kisview) override; + QSize sizeHint() const override; public Q_SLOTS: - void slotUpdateIcons(); - void slotUpdateFrameCache(); + void setPlaying(bool playing); + +Q_SIGNALS: + void playPause(); + void stop(); + void forward(); + void back(); private: - struct Private; - const QScopedPointer m_d; + class QPushButton* buttonBack; + class QPushButton* buttonStop; + class QPushButton* buttonPlayPause; + class QPushButton* buttonForward; }; - -#endif +#endif // KISTRANSPORTCONTROLS_H diff --git a/libs/ui/widgets/kis_utility_title_bar.cpp b/libs/ui/widgets/kis_utility_title_bar.cpp new file mode 100644 index 0000000000..eed735c1f7 --- /dev/null +++ b/libs/ui/widgets/kis_utility_title_bar.cpp @@ -0,0 +1,70 @@ +#include "kis_utility_title_bar.h" + +#include +#include +#include +#include +#include + +#include + +KisUtilityTitleBar::KisUtilityTitleBar(QWidget *parent) + : KisUtilityTitleBar(new QLabel(i18n("Title"), parent), parent) +{ +} + +KisUtilityTitleBar::KisUtilityTitleBar(QLabel *title, QWidget *parent) + : QWidget(parent) +{ + // Set up main layout.. + QHBoxLayout *mainLayout = new QHBoxLayout(this); + QMargins margins = mainLayout->contentsMargins(); + margins.setTop(0); + margins.setBottom(0); + mainLayout->setContentsMargins(margins); + mainLayout->setSpacing(0); + setLayout(mainLayout); + + // Set up title.. + mainLayout->addWidget(title); + + mainLayout->addSpacing(SPACING_UNIT * 2); + + // Set up widget area.. + widgetArea = new QHBoxLayout(this); + widgetArea->setSpacing(0); + widgetArea->setContentsMargins(0,0,0,0); + QWidget *widgetAreaWidget = new QWidget(this); + widgetAreaWidget->setLayout(widgetArea); + mainLayout->addWidget(widgetAreaWidget); + + mainLayout->addSpacing(SPACING_UNIT * 2); + + { // Controls/Decorations.. + QHBoxLayout *sublayout = new QHBoxLayout(this); + sublayout->setSpacing(0); + sublayout->setContentsMargins(0,0,0,0); + + QDockWidget *dockWidget = qobject_cast(parentWidget()); + + { // Float button... + QPushButton *button = new QPushButton(style()->standardIcon(QStyle::SP_TitleBarNormalButton), "", this); + button->setFlat(true); + connect(button, &QPushButton::clicked, [this, dockWidget](){ + dockWidget->setFloating(!dockWidget->isFloating()); + } ); + sublayout->addWidget(button); + } + + { // Close button... + QPushButton *button = new QPushButton(style()->standardIcon(QStyle::SP_DockWidgetCloseButton), "", this); + button->setFlat(true); + connect(button, SIGNAL(clicked(bool)), dockWidget, SLOT(close())); + sublayout->addWidget(button); + } + + QWidget *widget = new QWidget(); + widget->setLayout(sublayout); + mainLayout->addWidget(widget); + } +} diff --git a/plugins/dockers/animation/timeline_docker.h b/libs/ui/widgets/kis_utility_title_bar.h similarity index 51% copy from plugins/dockers/animation/timeline_docker.h copy to libs/ui/widgets/kis_utility_title_bar.h index d9a2261d7c..f820416391 100644 --- a/plugins/dockers/animation/timeline_docker.h +++ b/libs/ui/widgets/kis_utility_title_bar.h @@ -1,54 +1,56 @@ /* - * Copyright (c) 2015 Jouni Pentikäinen + * Copyright (c) 2020 Emmet O'Neill + * Copyright (c) 2020 Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _TIMELINE_DOCKER_H_ -#define _TIMELINE_DOCKER_H_ +#ifndef KISUTILITYTITLEBAR_H +#define KISUTILITYTITLEBAR_H -#include "kritaimage_export.h" +#include -#include -#include +#ifdef Q_OS_MACOS +#include +#endif -#include +#include "kritaui_export.h" -class KisCanvas2; -class KisAction; +class QLabel; +class QHBoxLayout; +class QPushButton; -class TimelineDocker : public QDockWidget, public KisMainwindowObserver +class KRITAUI_EXPORT KisUtilityTitleBar : public QWidget { Q_OBJECT + public: - TimelineDocker(); - ~TimelineDocker() override; + KisUtilityTitleBar(QWidget *parent = nullptr); + KisUtilityTitleBar(QLabel *title, QWidget *parent = nullptr); + + virtual QSize sizeHint() const {return QSize(32,32);} - QString observerName() override { return "TimelineDocker"; } - void setCanvas(KoCanvasBase *canvas) override; - void unsetCanvas() override; - void setViewManager(KisViewManager *kisview) override; +protected: + QLabel *title; + QHBoxLayout *widgetArea; -public Q_SLOTS: - void slotUpdateIcons(); - void slotUpdateFrameCache(); + const int SPACING_UNIT = 16; private: - struct Private; - const QScopedPointer m_d; + QPushButton *floatButton; + QPushButton *closeButton; }; - -#endif +#endif // KISUTILITYTITLEBAR_H diff --git a/libs/widgetutils/kis_action_registry.cpp b/libs/widgetutils/kis_action_registry.cpp index f1d9887c91..cd3e3e6ccf 100644 --- a/libs/widgetutils/kis_action_registry.cpp +++ b/libs/widgetutils/kis_action_registry.cpp @@ -1,426 +1,428 @@ /* * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "KoResourcePaths.h" #include "kis_icon_utils.h" #include "kis_action_registry.h" #include "kshortcutschemeshelper_p.h" namespace { /** * We associate several pieces of information with each shortcut. The first * piece of information is a QDomElement, containing the raw data from the * .action XML file. The second and third are QKeySequences, the first of * which is the default shortcut, the last of which is any custom shortcut. * The last two are the KActionCollection and KActionCategory used to * organize the shortcut editor. */ struct ActionInfoItem { QDomElement xmlData; QString collectionName; QString categoryName; inline QList defaultShortcuts() const { return m_defaultShortcuts; } inline void setDefaultShortcuts(const QList &value) { m_defaultShortcuts = value; } inline QList customShortcuts() const { return m_customShortcuts; } inline void setCustomShortcuts(const QList &value, bool explicitlyReset) { m_customShortcuts = value; m_explicitlyReset = explicitlyReset; } inline QList effectiveShortcuts() const { return m_customShortcuts.isEmpty() && !m_explicitlyReset ? m_defaultShortcuts : m_customShortcuts; } private: QList m_defaultShortcuts; QList m_customShortcuts; bool m_explicitlyReset = false; }; // Convenience macros to extract text of a child node. QString getChildContent(QDomElement xml, QString node) { return xml.firstChildElement(node).text(); } // Use Krita debug logging categories instead of KDE's default qDebug() for // harmless empty strings and translations QString quietlyTranslate(const QString &s) { if (s.isEmpty()) { return s; } QString translatedString = i18nc("action", s.toUtf8()); if (translatedString == s) { translatedString = i18n(s.toUtf8()); } if (translatedString.isEmpty()) { dbgAction << "No translation found for" << s; return s; } return translatedString; } } class Q_DECL_HIDDEN KisActionRegistry::Private { public: Private(KisActionRegistry *_q) : q(_q) {} // This is the main place containing ActionInfoItems. QMap actionInfoList; void loadActionFiles(); void loadCustomShortcuts(QString filename = QStringLiteral("kritashortcutsrc")); // XXX: this adds a default item for the given name to the list of actioninfo objects! ActionInfoItem &actionInfo(const QString &name) { if (!actionInfoList.contains(name)) { dbgAction << "Tried to look up info for unknown action" << name; } return actionInfoList[name]; } KisActionRegistry *q; QSet sanityPropertizedShortcuts; }; Q_GLOBAL_STATIC(KisActionRegistry, s_instance) KisActionRegistry *KisActionRegistry::instance() { if (!s_instance.exists()) { dbgRegistry << "initializing KoActionRegistry"; } return s_instance; } bool KisActionRegistry::hasAction(const QString &name) const { return d->actionInfoList.contains(name); } KisActionRegistry::KisActionRegistry() : d(new KisActionRegistry::Private(this)) { KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes"); QString schemeName = cg.readEntry("Current Scheme", "Default"); loadShortcutScheme(schemeName); loadCustomShortcuts(); } KisActionRegistry::~KisActionRegistry() { } KisActionRegistry::ActionCategory KisActionRegistry::fetchActionCategory(const QString &name) const { if (!d->actionInfoList.contains(name)) return ActionCategory(); const ActionInfoItem info = d->actionInfoList.value(name); return ActionCategory(info.collectionName, info.categoryName); } void KisActionRegistry::notifySettingsUpdated() { d->loadCustomShortcuts(); } void KisActionRegistry::loadCustomShortcuts() { d->loadCustomShortcuts(); } void KisActionRegistry::loadShortcutScheme(const QString &schemeName) { // Load scheme file if (schemeName != QStringLiteral("Default")) { QString schemeFileName = KShortcutSchemesHelper::schemeFileLocations().value(schemeName); if (schemeFileName.isEmpty()) { return; } KConfig schemeConfig(schemeFileName, KConfig::SimpleConfig); applyShortcutScheme(&schemeConfig); } else { // Apply default scheme, updating KisActionRegistry data applyShortcutScheme(); } } QAction * KisActionRegistry::makeQAction(const QString &name, QObject *parent) { QAction * a = new QAction(parent); if (!d->actionInfoList.contains(name)) { qWarning() << "Warning: requested data for unknown action" << name; a->setObjectName(name); return a; } propertizeAction(name, a); return a; } void KisActionRegistry::settingsPageSaved() { // For now, custom shortcuts are dealt with by writing to file and reloading. loadCustomShortcuts(); // Announce UI should reload current shortcuts. emit shortcutsUpdated(); } void KisActionRegistry::applyShortcutScheme(const KConfigBase *config) { // First, update the things in KisActionRegistry d->actionInfoList.clear(); d->loadActionFiles(); if (config == 0) { // Use default shortcut scheme. Simplest just to reload everything. loadCustomShortcuts(); } else { const auto schemeEntries = config->group(QStringLiteral("Shortcuts")).entryMap(); // Load info item for each shortcut, reset custom shortcuts auto it = schemeEntries.constBegin(); while (it != schemeEntries.end()) { ActionInfoItem &info = d->actionInfo(it.key()); info.setDefaultShortcuts(QKeySequence::listFromString(it.value())); it++; } } } void KisActionRegistry::updateShortcut(const QString &name, QAction *action) { const ActionInfoItem &info = d->actionInfo(name); action->setShortcuts(info.effectiveShortcuts()); action->setProperty("defaultShortcuts", QVariant::fromValue(info.defaultShortcuts())); d->sanityPropertizedShortcuts.insert(name); } bool KisActionRegistry::sanityCheckPropertized(const QString &name) { return d->sanityPropertizedShortcuts.contains(name); } QList KisActionRegistry::registeredShortcutIds() const { return d->actionInfoList.keys(); } bool KisActionRegistry::propertizeAction(const QString &name, QAction * a) { if (!d->actionInfoList.contains(name)) { warnAction << "propertizeAction: No XML data found for action" << name; return false; } const ActionInfoItem info = d->actionInfo(name); QDomElement actionXml = info.xmlData; if (!actionXml.text().isEmpty()) { // i18n requires converting format from QString. auto getChildContent_i18n = [=](QString node){return quietlyTranslate(getChildContent(actionXml, node));}; // Note: the fields in the .action documents marked for translation are determined by extractrc. QString icon = getChildContent(actionXml, "icon"); QString text = getChildContent_i18n("text"); QString whatsthis = getChildContent_i18n("whatsThis"); QString toolTip = getChildContent_i18n("toolTip"); QString statusTip = getChildContent_i18n("statusTip"); QString iconText = getChildContent_i18n("iconText"); bool isCheckable = getChildContent(actionXml, "isCheckable") == QString("true"); a->setObjectName(name); // This is helpful, should be added more places in Krita if (!icon.isEmpty()) { a->setIcon(KisIconUtils::loadIcon(icon.toLatin1())); } a->setText(text); a->setObjectName(name); a->setWhatsThis(whatsthis); a->setToolTip(toolTip); a->setStatusTip(statusTip); a->setIconText(iconText); a->setCheckable(isCheckable); } updateShortcut(name, a); return true; } QString KisActionRegistry::getActionProperty(const QString &name, const QString &property) { ActionInfoItem info = d->actionInfo(name); QDomElement actionXml = info.xmlData; if (actionXml.text().isEmpty()) { dbgAction << "getActionProperty: No XML data found for action" << name; return QString(); } return getChildContent(actionXml, property); } void KisActionRegistry::Private::loadActionFiles() { QStringList actionDefinitions = KoResourcePaths::findAllResources("kis_actions", "*.action", KoResourcePaths::Recursive); dbgAction << "Action Definitions" << actionDefinitions; // Extract actions all XML .action files. Q_FOREACH (const QString &actionDefinition, actionDefinitions) { - dbgAction << "\tLoading Action File" << actionDefinition; QDomDocument doc; QFile f(actionDefinition); f.open(QFile::ReadOnly); doc.setContent(f.readAll()); QDomElement base = doc.documentElement(); // "ActionCollection" outer group QString collectionName = base.attribute("name"); QString version = base.attribute("version"); if (version != "2") { - errAction << ".action XML file" << actionDefinition << "has incorrect version; skipping."; + qWarning() << ".action XML file" << actionDefinition << "has incorrect version; skipping."; continue; } // Loop over nodes. Each of these corresponds to a // KActionCategory, producing a group of actions in the shortcut dialog. QDomElement actions = base.firstChild().toElement(); while (!actions.isNull()) { // field QDomElement categoryTextNode = actions.firstChild().toElement(); QString categoryName = quietlyTranslate(categoryTextNode.text()); // tags QDomElement actionXml = categoryTextNode.nextSiblingElement(); + if (actionXml.isNull()) { + qWarning() << actionDefinition << "does not contain any valid actios! (Or the text element was left empty...)"; + } + // Loop over individual actions while (!actionXml.isNull()) { if (actionXml.tagName() == "Action") { // Read name from format QString name = actionXml.attribute("name"); - dbgAction << "\t\tloading xml data for action" << name; // Bad things if (name.isEmpty()) { - errAction << "Unnamed action in definitions file " << actionDefinition; + qWarning() << "Unnamed action in definitions file " << actionDefinition; } else if (actionInfoList.contains(name)) { qWarning() << "NOT COOL: Duplicated action name from xml data: " << name; } else { ActionInfoItem info; info.xmlData = actionXml; // Use empty list to signify no shortcut QString shortcutText = getChildContent(actionXml, "shortcut"); if (!shortcutText.isEmpty()) { info.setDefaultShortcuts(QKeySequence::listFromString(shortcutText)); } info.categoryName = categoryName; info.collectionName = collectionName; actionInfoList.insert(name,info); } } actionXml = actionXml.nextSiblingElement(); } actions = actions.nextSiblingElement(); } } } void KisActionRegistry::Private::loadCustomShortcuts(QString filename) { const KConfigGroup localShortcuts(KSharedConfig::openConfig(filename), QStringLiteral("Shortcuts")); if (!localShortcuts.exists()) { return; } // Distinguish between two "null" states for custom shortcuts. for (auto i = actionInfoList.begin(); i != actionInfoList.end(); ++i) { if (localShortcuts.hasKey(i.key())) { QString entry = localShortcuts.readEntry(i.key(), QString()); if (entry == QStringLiteral("none")) { i.value().setCustomShortcuts(QList(), true); } else { i.value().setCustomShortcuts(QKeySequence::listFromString(entry), false); } } else { i.value().setCustomShortcuts(QList(), false); } } } KisActionRegistry::ActionCategory::ActionCategory() { } KisActionRegistry::ActionCategory::ActionCategory(const QString &_componentName, const QString &_categoryName) : componentName(_componentName), categoryName(_categoryName), m_isValid(true) { } bool KisActionRegistry::ActionCategory::isValid() const { return m_isValid && !categoryName.isEmpty() && !componentName.isEmpty(); } diff --git a/packaging/windows/installer/MakeInstallerNsis.cmake.in b/packaging/windows/installer/MakeInstallerNsis.cmake.in index bee7ddc325..ba05c09878 100644 --- a/packaging/windows/installer/MakeInstallerNsis.cmake.in +++ b/packaging/windows/installer/MakeInstallerNsis.cmake.in @@ -1,167 +1,167 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) set(ARCH_IS_32_BIT @INSTALLER_NSIS_IS_32_BIT@) if(ARCH_IS_32_BIT) set(ARG_ARCH "/DKRITA_INSTALLER_32") set(FILENAME_ARCH "-x86") else() set(ARG_ARCH "/DKRITA_INSTALLER_64") set(FILENAME_ARCH "-x64") endif() if(NOT IS_DIRECTORY "${KRITA_PACKAGE_ROOT}") message(FATAL_ERROR "KRITA_PACKAGE_ROOT not set") endif() if(NOT DEFINED OUTPUT_FILEPATH) set(OUTPUT_FILEPATH "${CMAKE_CURRENT_BINARY_DIR}/krita-setup${FILENAME_ARCH}.exe") endif() if(NOT DEFINED DOWNLOAD_DIR) set(DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}") endif() # Download and find NSIS if(NOT DEFINED NO_DOWNLOAD_NSIS) set(DOWNLOAD_NSIS_VERSION "3.05") message(STATUS "Downloading NSIS...") if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}") file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}") endif() file(DOWNLOAD "https://files.kde.org/krita/build/dependencies/nsis-${DOWNLOAD_NSIS_VERSION}.zip" "${DOWNLOAD_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}.zip" - EXPECTED_HASH SHA1=68f9c7025110b95c20431bbce28f3bf2d0ddb1ff + EXPECTED_HASH SHA1=d0cf5c1397d3ffb5cf6643fbff46457e05a48312 ) execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${DOWNLOAD_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}.zip" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" RESULT_VARIABLE result_extract ) if(NOT result_extract EQUAL 0) message(FATAL_ERROR "Failed to extract NSIS") endif() if(NOT IS_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}") message(FATAL_ERROR "Failed to find NSIS after extracting") endif() find_program(TOOL_MAKENSIS NAMES makensis.exe PATHS "${CMAKE_CURRENT_BINARY_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}" NO_DEFAULT_PATH ) else() find_program(TOOL_MAKENSIS NAMES makensis.exe HINTS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS]" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS]" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS\\Unicode]" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode]" ) endif() if(NOT TOOL_MAKENSIS) message(FATAL_ERROR "Failed to find makensis.exe") endif() execute_process(COMMAND "${TOOL_MAKENSIS}" "/version" OUTPUT_VARIABLE MAKENSIS_VERSION ) # Check version is v3.* if(NOT MAKENSIS_VERSION MATCHES "^v3\\.") message(FATAL_ERROR "Expected NSIS version v3.*, got ${MAKENSIS_VERSION}") endif() message(STATUS "NSIS version ${MAKENSIS_VERSION}") # Check if package contains debug symbols file(TO_CMAKE_PATH "${KRITA_PACKAGE_ROOT}" KRITA_PACKAGE_ROOT_PATCHED) file(GLOB_RECURSE globForDebugFiles "${KRITA_PACKAGE_ROOT_PATCHED}/*.debug" ) if(globForDebugFiles) if(REMOVE_DEBUG) message(STATUS "Removing debug symbols") foreach(debugFileItem ${globForDebugFiles}) get_filename_component(debugFileDir "${debugFileItem}" DIRECTORY) get_filename_component(debugDirName "${debugFileDir}" NAME) if(debugDirName STREQUAL ".debug") if(EXISTS "${debugFileDir}") message(STATUS "Deleting ${debugFileDir}") file(REMOVE_RECURSE "${debugFileDir}") endif() else() message(STATUS "Deleting ${debugFileItem}") file(REMOVE "${debugFileItem}") endif() endforeach() else() message(FATAL_ERROR "${KRITA_PACKAGE_ROOT} seems to contain debug symbols. Set REMOVE_DEBUG to true if you want to remove them.") endif() endif() # Download installer script package message(STATUS "Downloading NSIS script package...") if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis") file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis") endif() file(DOWNLOAD "https://github.com/alvinhochun/KritaShellExtension/releases/download/v1.2.4b/krita-nsis-v1.2.4b.zip" "${DOWNLOAD_DIR}/krita-nsis.zip" EXPECTED_HASH SHA1=43c5bade13fb885e8546dac4486b8e2df62dc692 ) execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${DOWNLOAD_DIR}/krita-nsis.zip" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" RESULT_VARIABLE result_extract ) if(NOT result_extract EQUAL 0) message(FATAL_ERROR "Failed to extract krita-nsis package") endif() if(NOT IS_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis") message(FATAL_ERROR "Failed to find krita-nsis after extracting") endif() # Place Krita installer files in the right place file(COPY "${CMAKE_CURRENT_LIST_DIR}/installer/" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis/" FILES_MATCHING PATTERN "*" ) # Detect FFmpeg (currently optional) if(EXISTS "${KRITA_PACKAGE_ROOT}/bin/ffmpeg.exe" AND EXISTS "${KRITA_PACKAGE_ROOT}/bin/ffmpeg_LICENSE.txt" AND EXISTS "${KRITA_PACKAGE_ROOT}/bin/ffmpeg_README.txt" ) set(ARG_HAS_FFMPEG "/DHAS_FFMPEG") else() set(ARG_HAS_FFMPEG "/DNO_HAS_FFMPEG") endif() # Build installer message(STATUS "Building installer...") set(KRITA_VERSION_NUMBER "@KRITA_STABLE_VERSION_MAJOR@.@KRITA_STABLE_VERSION_MINOR@.@KRITA_VERSION_RELEASE@.@KRITA_VERSION_REVISION@") set(KRITA_VERSION_STRING "@KRITA_VERSION_STRING@") set(KRITA_GIT_SHA1_STRING "@KRITA_GIT_SHA1_STRING@") if(KRITA_GIT_SHA1_STRING) set(KRITA_VERSION_STRING "${KRITA_VERSION_STRING} (git ${KRITA_GIT_SHA1_STRING})") endif() execute_process(COMMAND "${TOOL_MAKENSIS}" "${ARG_ARCH}" "/DKRITA_VERSION=${KRITA_VERSION_NUMBER}" "/DKRITA_VERSION_DISPLAY=${KRITA_VERSION_STRING}" "/DKRITA_INSTALLER_OUTPUT_DIR=" "/DKRITA_INSTALLER_OUTPUT_NAME=${OUTPUT_FILEPATH}" "/DKRITA_PACKAGE_ROOT=${KRITA_PACKAGE_ROOT}" "${ARG_HAS_FFMPEG}" "/XSetCompressor /SOLID lzma" "/V3" "/INPUTCHARSET" "UTF8" "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis/installer_krita.nsi" RESULT_VARIABLE result_makensis ) if(NOT result_makensis EQUAL 0) message(FATAL_ERROR "Failed to build installer") endif() message(STATUS "Built installer") diff --git a/plugins/assistants/Assistants/KisAssistantTool.action b/plugins/assistants/Assistants/KisAssistantTool.action index fe7e843908..24cf45315e 100644 --- a/plugins/assistants/Assistants/KisAssistantTool.action +++ b/plugins/assistants/Assistants/KisAssistantTool.action @@ -1,6 +1,16 @@ - - Assistant Tool + + Tool Shortcuts + + + Assistant Tool + + Assistant Tool + Assistant Tool + + false + + diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector.cpp index 10a3741820..3a73876486 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector.cpp @@ -1,415 +1,423 @@ /* * Copyright (c) 2010 Adam Celarek * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_color_selector_ring.h" #include "kis_color_selector_triangle.h" #include "kis_color_selector_simple.h" #include "kis_color_selector_wheel.h" #include "kis_color_selector_container.h" #include "kis_canvas2.h" #include "kis_signal_compressor.h" #include "KisViewManager.h" KisColorSelector::KisColorSelector(KisColorSelectorConfiguration conf, QWidget* parent) : KisColorSelectorBase(parent), m_ring(0), m_triangle(0), m_slider(0), m_square(0), m_wheel(0), m_mainComponent(0), m_subComponent(0), m_grabbingComponent(0), m_blipDisplay(true) { init(); updateSettings(); setConfiguration(conf); } KisColorSelector::KisColorSelector(QWidget* parent) : KisColorSelectorBase(parent), m_ring(0), m_triangle(0), m_slider(0), m_square(0), m_wheel(0), m_button(0), m_mainComponent(0), m_subComponent(0), m_grabbingComponent(0), m_blipDisplay(true) { init(); updateSettings(); } KisColorSelectorBase* KisColorSelector::createPopup() const { KisColorSelectorBase* popup = new KisColorSelector(0); popup->setColor(m_lastRealColor); return popup; } void KisColorSelector::setConfiguration(KisColorSelectorConfiguration conf) { m_configuration = conf; if(m_mainComponent!=0) { Q_ASSERT(m_subComponent!=0); m_mainComponent->setGeometry(0, 0, 0, 0); m_subComponent->setGeometry(0, 0, 0, 0); m_mainComponent->disconnect(); m_subComponent->disconnect(); } switch (m_configuration.mainType) { case KisColorSelectorConfiguration::Square: m_mainComponent=m_square; break; case KisColorSelectorConfiguration::Wheel: m_mainComponent=m_wheel; break; case KisColorSelectorConfiguration::Triangle: m_mainComponent=m_triangle; break; default: Q_ASSERT(false); } switch (m_configuration.subType) { case KisColorSelectorConfiguration::Ring: m_subComponent=m_ring; break; case KisColorSelectorConfiguration::Slider: m_subComponent=m_slider; break; default: Q_ASSERT(false); } connect(m_mainComponent, SIGNAL(paramChanged(qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal)), m_subComponent, SLOT(setParam(qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal)), Qt::UniqueConnection); connect(m_subComponent, SIGNAL(paramChanged(qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal)), m_mainComponent, SLOT(setParam(qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal)), Qt::UniqueConnection); connect(m_mainComponent, SIGNAL(update()), m_signalCompressor, SLOT(start()), Qt::UniqueConnection); connect(m_subComponent, SIGNAL(update()), m_signalCompressor, SLOT(start()), Qt::UniqueConnection); m_mainComponent->setConfiguration(m_configuration.mainTypeParameter, m_configuration.mainType); m_subComponent->setConfiguration(m_configuration.subTypeParameter, m_configuration.subType); QResizeEvent event(QSize(width(), height()), QSize()); resizeEvent(&event); } KisColorSelectorConfiguration KisColorSelector::configuration() const { return m_configuration; } void KisColorSelector::updateSettings() { KisColorSelectorBase::updateSettings(); KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); setConfiguration(KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()))); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->canvasResourceProvider()) { bool gamutMaskActive = m_canvas->viewManager()->canvasResourceProvider()->gamutMaskActive(); if (gamutMaskActive) { KoGamutMaskSP currentMask = m_canvas->viewManager()->canvasResourceProvider()->currentGamutMask(); if (currentMask) { slotGamutMaskSet(currentMask); } } else { slotGamutMaskToggle(false); } } } void KisColorSelector::slotGamutMaskSet(KoGamutMaskSP gamutMask) { m_mainComponent->setGamutMask(gamutMask); m_subComponent->setGamutMask(gamutMask); slotGamutMaskToggle(true); } void KisColorSelector::slotGamutMaskUnset() { m_mainComponent->unsetGamutMask(); m_subComponent->unsetGamutMask(); slotGamutMaskToggle(false); } void KisColorSelector::slotGamutMaskPreviewUpdate() { m_mainComponent->updateGamutMaskPreview(); m_subComponent->updateGamutMaskPreview(); } void KisColorSelector::slotGamutMaskDeactivate() { slotGamutMaskToggle(false); } void KisColorSelector::slotGamutMaskToggle(bool state) { m_mainComponent->toggleGamutMask(state); m_subComponent->toggleGamutMask(state); } void KisColorSelector::updateIcons() { if (m_button) { m_button->setIcon(KisIconUtils::loadIcon("configure")); } } void KisColorSelector::hasAtLeastOneDocument(bool value) { m_hasAtLeastOneDocumentOpen = value; } void KisColorSelector::reset() { if (m_mainComponent) { m_mainComponent->setDirty(); } if (m_subComponent) { m_subComponent->setDirty(); } KisColorSelectorBase::reset(); } void KisColorSelector::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter p(this); - p.fillRect(0,0,width(), height(), QColor(128,128,128)); + KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); + + // If checked, use theme colors for background of selector + if (cfg.readEntry("useCustomColorForBackground", false)) { + p.fillRect(0,0,width(), height(), cfg.readEntry("customSelectorBackgroundColor", QColor(Qt::gray))); + } else { + p.fillRect(0,0,width(), height(), qApp->palette().window().color()); + } + p.setRenderHint(QPainter::Antialiasing); // this variable name isn't entirely accurate to what always happens. see definition in header file to understand it better if (!m_hasAtLeastOneDocumentOpen) { p.setOpacity(0.2); } m_mainComponent->paintEvent(&p); m_subComponent->paintEvent(&p); p.setOpacity(1.0); } inline int iconSize(qreal width, qreal height) { qreal radius = qMin(width, height)/2.; qreal xm = width/2.; qreal ym = height/2.; if(xm>=2*ym || ym>=2*xm) return qBound(5., radius, 32.); qreal a=-2; qreal b=2.*(xm+ym); qreal c=radius*radius-xm*xm-ym*ym; return qBound(5., ((-b+sqrt(b*b-4*a*c))/(2*a)), 32.); } void KisColorSelector::resizeEvent(QResizeEvent* e) { if (m_configuration.subType == KisColorSelectorConfiguration::Ring) { m_ring->setGeometry(0,0,width(), height()); if (displaySettingsButton()) { int size = iconSize(width(), height()); m_button->setGeometry(0, 0, size, size); } if (m_configuration.mainType == KisColorSelectorConfiguration::Triangle) { m_triangle->setGeometry(width()/2-m_ring->innerRadius(), height()/2-m_ring->innerRadius(), m_ring->innerRadius()*2, m_ring->innerRadius()*2); } else { int size = m_ring->innerRadius()*2/sqrt(2.); m_square->setGeometry(width()/2-size/2, height()/2-size/2, size, size); } } else { // type wheel and square if (m_configuration.mainType == KisColorSelectorConfiguration::Wheel) { if(displaySettingsButton()) { int size = iconSize(width(), height()*0.9); m_button->setGeometry(0, height()*0.1, size, size); } m_mainComponent->setGeometry(0, height()*0.1, width(), height()*0.9); m_subComponent->setGeometry( 0, 0, width(), height()*0.1); } else { int buttonSize = 0; if(displaySettingsButton()) { buttonSize = qBound(20, int(0.1*height()), 32); m_button->setGeometry(0, 0, buttonSize, buttonSize); } if(height()>width()) { int selectorHeight=height()-buttonSize; m_mainComponent->setGeometry(0, buttonSize+selectorHeight*0.1, width(), selectorHeight*0.9); m_subComponent->setGeometry( 0, buttonSize, width(), selectorHeight*0.1); } else { int selectorWidth=width()-buttonSize; m_mainComponent->setGeometry(buttonSize, height()*0.1, selectorWidth, height()*0.9); m_subComponent->setGeometry( buttonSize, 0, selectorWidth, height()*0.1); } } } // reset the correct color after resizing the widget setColor(m_lastRealColor); KisColorSelectorBase::resizeEvent(e); } void KisColorSelector::mousePressEvent(QMouseEvent* e) { e->setAccepted(false); KisColorSelectorBase::mousePressEvent(e); if(!e->isAccepted()) { if(m_mainComponent->wantsGrab(e->x(), e->y())) m_grabbingComponent=m_mainComponent; else if(m_subComponent->wantsGrab(e->x(), e->y())) m_grabbingComponent=m_subComponent; mouseEvent(e); updatePreviousColorPreview(); e->accept(); } } void KisColorSelector::mouseMoveEvent(QMouseEvent* e) { KisColorSelectorBase::mouseMoveEvent(e); mouseEvent(e); e->accept(); } void KisColorSelector::mouseReleaseEvent(QMouseEvent* e) { e->setAccepted(false); KisColorSelectorBase::mouseReleaseEvent(e); if(!e->isAccepted() && !(m_lastRealColor == m_currentRealColor)) { m_lastRealColor = m_currentRealColor; m_lastColorRole = Acs::buttonToRole(e->button()); updateColor(m_lastRealColor, m_lastColorRole, false); updateBaseColorPreview(m_currentRealColor); e->accept(); } m_grabbingComponent=0; } bool KisColorSelector::displaySettingsButton() { return dynamic_cast(parent()); } void KisColorSelector::setColor(const KoColor &color) { m_mainComponent->setColor(color); m_subComponent->setColor(color); m_lastRealColor = color; m_signalCompressor->start(); } void KisColorSelector::mouseEvent(QMouseEvent *e) { if (m_grabbingComponent && (e->buttons() & Qt::LeftButton || e->buttons() & Qt::RightButton)) { m_grabbingComponent->mouseEvent(e->x(), e->y()); KoColor color = m_mainComponent->currentColor(); Acs::ColorRole role = Acs::buttonsToRole(e->button(), e->buttons()); m_currentRealColor = color; requestUpdateColorAndPreview(color, role); } } void KisColorSelector::init() { setAcceptDrops(true); m_lastColorRole = Acs::Foreground; m_ring = new KisColorSelectorRing(this); m_triangle = new KisColorSelectorTriangle(this); m_slider = new KisColorSelectorSimple(this); m_square = new KisColorSelectorSimple(this); m_wheel = new KisColorSelectorWheel(this); if(displaySettingsButton()) { m_button = new QPushButton(this); m_button->setIcon(KisIconUtils::loadIcon("configure")); m_button->setFlat(true); connect(m_button, SIGNAL(clicked()), SIGNAL(settingsButtonClicked())); } // a tablet can send many more signals, than a mouse // this causes many repaints, if updating after every signal. m_signalCompressor = new KisSignalCompressor(20, KisSignalCompressor::FIRST_INACTIVE, this); connect(m_signalCompressor, SIGNAL(timeout()), SLOT(update())); setMinimumSize(40, 40); } diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_container.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_container.cpp index e44952784e..a001b2f142 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_container.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_container.cpp @@ -1,229 +1,232 @@ /* * Copyright (c) 2010 Adam Celarek * * 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 */ #include "kis_color_selector_container.h" #include "kis_color_selector.h" #include "kis_my_paint_shade_selector.h" #include "kis_minimal_shade_selector.h" #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include "kis_node_manager.h" #include "kis_node.h" #include "kis_paint_device.h" #include "kis_action_registry.h" KisColorSelectorContainer::KisColorSelectorContainer(QWidget *parent) : QWidget(parent), m_colorSelector(new KisColorSelector(this)), m_myPaintShadeSelector(new KisMyPaintShadeSelector(this)), m_minimalShadeSelector(new KisMinimalShadeSelector(this)), m_shadeSelector(m_myPaintShadeSelector), m_gamutMaskToolbar(new KisGamutMaskToolbar(this)), m_showColorSelector(true), m_canvas(0) { m_widgetLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); m_widgetLayout->setSpacing(0); m_widgetLayout->setMargin(0); m_gamutMaskToolbar->setContentsMargins(0, 0, 0, 5); m_gamutMaskToolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_colorSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_myPaintShadeSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_minimalShadeSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_widgetLayout->addWidget(m_gamutMaskToolbar); m_widgetLayout->addWidget(m_colorSelector); m_widgetLayout->addWidget(m_myPaintShadeSelector); m_widgetLayout->addWidget(m_minimalShadeSelector); m_gamutMaskToolbar->hide(); m_myPaintShadeSelector->hide(); m_minimalShadeSelector->hide(); connect(m_colorSelector,SIGNAL(settingsButtonClicked()), SIGNAL(openSettings())); connect(this, SIGNAL(settingsChanged()), m_colorSelector, SLOT(updateSettings())); connect(this, SIGNAL(settingsChanged()), m_myPaintShadeSelector, SLOT(updateSettings())); connect(this, SIGNAL(settingsChanged()), this, SLOT(updateSettings())); connect(this, SIGNAL(settingsChanged()), m_minimalShadeSelector, SLOT(updateSettings())); m_colorSelAction = KisActionRegistry::instance()->makeQAction("show_color_selector", this); connect(m_colorSelAction, SIGNAL(triggered()), m_colorSelector, SLOT(showPopup()), Qt::UniqueConnection); m_mypaintAction = KisActionRegistry::instance()->makeQAction("show_mypaint_shade_selector", this); connect(m_mypaintAction, SIGNAL(triggered()), m_myPaintShadeSelector, SLOT(showPopup()), Qt::UniqueConnection); m_minimalAction = KisActionRegistry::instance()->makeQAction("show_minimal_shade_selector", this); connect(m_minimalAction, SIGNAL(triggered()), m_minimalShadeSelector, SLOT(showPopup()), Qt::UniqueConnection); } void KisColorSelectorContainer::unsetCanvas() { m_colorSelector->hasAtLeastOneDocument(doesAtleastOneDocumentExist()); m_colorSelector->unsetCanvas(); m_myPaintShadeSelector->unsetCanvas(); m_minimalShadeSelector->unsetCanvas(); m_canvas = 0; } bool KisColorSelectorContainer::doesAtleastOneDocumentExist() { if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document() ) { if (m_canvas->viewManager()->document()->image()->height() == 0) { return false; } else { return true; } } else { return false; } } void KisColorSelectorContainer::slotUpdateIcons() { m_colorSelector->updateIcons(); + m_colorSelector->updateSettings(); + m_minimalShadeSelector->updateSettings(); + m_myPaintShadeSelector->updateSettings(); } void KisColorSelectorContainer::setCanvas(KisCanvas2* canvas) { if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_canvas->viewManager()->nodeManager()->disconnect(this); KActionCollection *ac = m_canvas->viewManager()->actionCollection(); ac->takeAction(ac->action("show_color_selector")); ac->takeAction(ac->action("show_mypaint_shade_selector")); ac->takeAction(ac->action("show_minimal_shade_selector")); } m_canvas = canvas; m_colorSelector->setCanvas(canvas); m_myPaintShadeSelector->setCanvas(canvas); m_minimalShadeSelector->setCanvas(canvas); m_colorSelector->hasAtLeastOneDocument(doesAtleastOneDocumentExist()); if (m_canvas && m_canvas->viewManager()) { connect(m_canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigGamutMaskChanged(KoGamutMaskSP)), m_colorSelector, SLOT(slotGamutMaskSet(KoGamutMaskSP)), Qt::UniqueConnection); connect(m_canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigGamutMaskUnset()), m_colorSelector, SLOT(slotGamutMaskUnset()), Qt::UniqueConnection); connect(m_canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigGamutMaskPreviewUpdate()), m_colorSelector, SLOT(slotGamutMaskPreviewUpdate()), Qt::UniqueConnection); connect(m_canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigGamutMaskDeactivated()), m_colorSelector, SLOT(slotGamutMaskDeactivate()), Qt::UniqueConnection); m_gamutMaskToolbar->connectMaskSignals(m_canvas->viewManager()->canvasResourceProvider()); KActionCollection* actionCollection = canvas->viewManager()->actionCollection(); actionCollection->addAction("show_color_selector", m_colorSelAction); actionCollection->addAction("show_mypaint_shade_selector", m_mypaintAction); actionCollection->addAction("show_minimal_shade_selector", m_minimalAction); } } void KisColorSelectorContainer::updateSettings() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_onDockerResizeSetting = (int)cfg.readEntry("onDockerResize", 0); m_showColorSelector = (bool) cfg.readEntry("showColorSelector", true); if (m_showColorSelector) { m_colorSelector->show(); if (m_colorSelector->configuration().mainType == KisColorSelectorConfiguration::Wheel) { m_gamutMaskToolbar->show(); } else { m_gamutMaskToolbar->hide(); } } else { m_colorSelector->hide(); m_gamutMaskToolbar->hide(); } QString type = cfg.readEntry("shadeSelectorType", "Minimal"); QWidget* newShadeSelector; if(type=="MyPaint") newShadeSelector = m_myPaintShadeSelector; else if (type=="Minimal") newShadeSelector = m_minimalShadeSelector; else newShadeSelector = 0; if(m_shadeSelector!=newShadeSelector && m_shadeSelector!=0) { m_shadeSelector->hide(); } m_shadeSelector=newShadeSelector; if(m_shadeSelector!=0) m_shadeSelector->show(); } void KisColorSelectorContainer::resizeEvent(QResizeEvent * e) { if(m_shadeSelector!=0) { int minimumHeightForBothWidgets = m_colorSelector->minimumHeight()+m_shadeSelector->minimumHeight()+30; //+30 for the buttons (temporarily) if(height()hide(); } else { m_shadeSelector->show(); } // m_onDockerResizeSetting==0 is allow horizontal layout if(height() < width() && m_onDockerResizeSetting==0 && m_shadeSelector!=m_minimalShadeSelector) { m_widgetLayout->setDirection(QBoxLayout::LeftToRight); } else { m_widgetLayout->setDirection(QBoxLayout::TopToBottom); } } QWidget::resizeEvent(e); } diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_settings.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_settings.cpp index 86d688fa15..15f7f259dc 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_settings.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_settings.cpp @@ -1,632 +1,650 @@ /* * Copyright (C) 2010 Celarek Adam * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_selector_settings.h" #include "ui_wdg_color_selector_settings.h" #include #include #include #include #include #include #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorProfile.h" #include "kis_color_selector_combo_box.h" #include "kis_color_selector.h" #include "kis_config.h" KisColorSelectorSettings::KisColorSelectorSettings(QWidget *parent) : KisPreferenceSet(parent), ui(new Ui::KisColorSelectorSettings) { ui->setupUi(this); resize(minimumSize()); ui->colorSelectorConfiguration->setColorSpace(ui->colorSpace->currentColorSpace()); ui->useDifferentColorSpaceCheckbox->setChecked(false); connect(ui->useDifferentColorSpaceCheckbox, SIGNAL(clicked(bool)), this, SLOT(useDifferentColorSpaceChecked(bool))); /* color docker selector drop down */ ui->dockerColorSettingsComboBox->addItem(i18n("Advanced Color Selector")); //ui->dockerColorSettingsComboBox->addItem(i18n("Color Sliders")); ui->dockerColorSettingsComboBox->addItem(i18n("Color Hotkeys")); ui->dockerColorSettingsComboBox->setCurrentIndex(0); // start off seeing advanced color selector properties connect( ui->dockerColorSettingsComboBox, SIGNAL(currentIndexChanged(int)),this, SLOT(changedColorDocker(int))); changedColorDocker(0); /* advanced color docker options */ ui->dockerResizeOptionsComboBox->addItem(i18n("Change to a Horizontal Layout")); ui->dockerResizeOptionsComboBox->addItem(i18n("Hide Shade Selector")); ui->dockerResizeOptionsComboBox->addItem(i18n("Do Nothing")); ui->dockerResizeOptionsComboBox->setCurrentIndex(0); ui->zoomSelectorOptionComboBox->addItem(i18n("When Pressing Middle Mouse Button")); ui->zoomSelectorOptionComboBox->addItem(i18n("On Mouse Over")); ui->zoomSelectorOptionComboBox->addItem(i18n("Never")); ui->zoomSelectorOptionComboBox->setCurrentIndex(0); ui->colorSelectorTypeComboBox->addItem(i18n("HSV")); ui->colorSelectorTypeComboBox->addItem(i18n("HSL")); ui->colorSelectorTypeComboBox->addItem(i18n("HSI")); ui->colorSelectorTypeComboBox->addItem(i18n("HSY'")); ui->colorSelectorTypeComboBox->setCurrentIndex(0); connect( ui->colorSelectorTypeComboBox, SIGNAL(currentIndexChanged(int)),this, SLOT(changedACSColorSelectorType(int))); changedACSColorSelectorType(0); // initialize everything to HSV at the start ui->ACSshadeSelectorMyPaintColorModelComboBox->addItem(i18n("HSV")); ui->ACSshadeSelectorMyPaintColorModelComboBox->addItem(i18n("HSL")); ui->ACSshadeSelectorMyPaintColorModelComboBox->addItem(i18n("HSI")); ui->ACSshadeSelectorMyPaintColorModelComboBox->addItem(i18n("HSY'")); ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(0); ui->ACSShadeSelectorTypeComboBox->addItem(i18n("MyPaint")); ui->ACSShadeSelectorTypeComboBox->addItem(i18n("Minimal")); ui->ACSShadeSelectorTypeComboBox->addItem(i18n("Do Not Show")); ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(0); changedACSShadeSelectorType(0); // show/hide UI elements for MyPaint settings connect( ui->ACSShadeSelectorTypeComboBox, SIGNAL(currentIndexChanged(int)),this, SLOT(changedACSShadeSelectorType(int))); ui->commonColorsAlignVertical->setChecked(true); ui->commonColorsAlignHorizontal->setChecked(true); connect( ui->commonColorsAlignHorizontal, SIGNAL(toggled(bool)), this, SLOT(changedACSColorAlignment(bool))); connect( ui->lastUsedColorsAlignHorizontal, SIGNAL(toggled(bool)), this, SLOT(changedACSLastUsedColorAlignment(bool))); changedACSColorAlignment(ui->commonColorsAlignHorizontal->isChecked()); changedACSLastUsedColorAlignment(ui->lastUsedColorsAlignHorizontal->isChecked()); connect(ui->colorSpace, SIGNAL(colorSpaceChanged(const KoColorSpace*)), ui->colorSelectorConfiguration, SLOT(setColorSpace(const KoColorSpace*))); connect(this, SIGNAL(hsxchanged(int)), ui->colorSelectorConfiguration, SLOT(setList(int))); connect(ui->minimalShadeSelectorLineCount, SIGNAL(valueChanged(int)), ui->minimalShadeSelectorLineSettings, SLOT(setLineCount(int))); connect(ui->minimalShadeSelectorLineSettings, SIGNAL(lineCountChanged(int)), ui->minimalShadeSelectorLineCount, SLOT(setValue(int))); connect(ui->minimalShadeSelectorAsGradient, SIGNAL(toggled(bool)), ui->minimalShadeSelectorLineSettings, SIGNAL(setGradient(bool))); connect(ui->minimalShadeSelectorAsColorPatches, SIGNAL(toggled(bool)), ui->minimalShadeSelectorLineSettings, SIGNAL(setPatches(bool))); connect(ui->minimalShadeSelectorLineHeight, SIGNAL(valueChanged(int)), ui->minimalShadeSelectorLineSettings, SIGNAL(setLineHeight(int))); connect(ui->minimalShadeSelectorPatchesPerLine, SIGNAL(valueChanged(int)), ui->minimalShadeSelectorLineSettings, SIGNAL(setPatchCount(int))); } KisColorSelectorSettings::~KisColorSelectorSettings() { delete ui; } QString KisColorSelectorSettings::id() { return QString("advancedColorSelector"); } QString KisColorSelectorSettings::name() { return header(); } QString KisColorSelectorSettings::header() { return QString(i18n("Color Selector Settings")); } QIcon KisColorSelectorSettings::icon() { return KisIconUtils::loadIcon("extended_color_selector"); } void KisColorSelectorSettings::savePreferences() const { // write cfg KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); KConfigGroup hsxcfg = KSharedConfig::openConfig()->group("hsxColorSlider"); KConfigGroup hotkeycfg = KSharedConfig::openConfig()->group("colorhotkeys"); // advanced color selector cfg.writeEntry("onDockerResize", ui->dockerResizeOptionsComboBox->currentIndex()); cfg.writeEntry("zoomSelectorOptions", ui->zoomSelectorOptionComboBox->currentIndex() ); cfg.writeEntry("zoomSize", ui->popupSize->value()); cfg.writeEntry("showColorSelector", ui->chkShowColorSelector->isChecked()); bool useCustomColorSpace = ui->useDifferentColorSpaceCheckbox->isChecked(); const KoColorSpace* colorSpace = useCustomColorSpace ? ui->colorSpace->currentColorSpace() : 0; KisConfig kisconfig(false); kisconfig.setCustomColorSelectorColorSpace(colorSpace); //color patches cfg.writeEntry("lastUsedColorsShow", ui->lastUsedColorsShow->isChecked()); cfg.writeEntry("lastUsedColorsAlignment", ui->lastUsedColorsAlignVertical->isChecked()); cfg.writeEntry("lastUsedColorsScrolling", ui->lastUsedColorsAllowScrolling->isChecked()); cfg.writeEntry("lastUsedColorsNumCols", ui->lastUsedColorsNumCols->value()); cfg.writeEntry("lastUsedColorsNumRows", ui->lastUsedColorsNumRows->value()); cfg.writeEntry("lastUsedColorsCount", ui->lastUsedColorsPatchCount->value()); cfg.writeEntry("lastUsedColorsWidth", ui->lastUsedColorsWidth->value()); cfg.writeEntry("lastUsedColorsHeight", ui->lastUsedColorsHeight->value()); cfg.writeEntry("commonColorsShow", ui->commonColorsShow->isChecked()); cfg.writeEntry("commonColorsAlignment", ui->commonColorsAlignVertical->isChecked()); cfg.writeEntry("commonColorsScrolling", ui->commonColorsAllowScrolling->isChecked()); cfg.writeEntry("commonColorsNumCols", ui->commonColorsNumCols->value()); cfg.writeEntry("commonColorsNumRows", ui->commonColorsNumRows->value()); cfg.writeEntry("commonColorsCount", ui->commonColorsPatchCount->value()); cfg.writeEntry("commonColorsWidth", ui->commonColorsWidth->value()); cfg.writeEntry("commonColorsHeight", ui->commonColorsHeight->value()); cfg.writeEntry("commonColorsAutoUpdate", ui->commonColorsAutoUpdate->isChecked()); //shade selector int shadeSelectorTypeIndex = ui->ACSShadeSelectorTypeComboBox->currentIndex(); if(shadeSelectorTypeIndex == 0) { cfg.writeEntry("shadeSelectorType", "MyPaint"); } else if (shadeSelectorTypeIndex == 1) { cfg.writeEntry("shadeSelectorType", "Minimal"); } else { cfg.writeEntry("shadeSelectorType", "Hidden"); } cfg.writeEntry("shadeSelectorUpdateOnRightClick", ui->shadeSelectorUpdateOnRightClick->isChecked()); cfg.writeEntry("shadeSelectorUpdateOnForeground", ui->shadeSelectorUpdateOnForeground->isChecked()); cfg.writeEntry("shadeSelectorUpdateOnLeftClick", ui->shadeSelectorUpdateOnLeftClick->isChecked()); cfg.writeEntry("shadeSelectorUpdateOnBackground", ui->shadeSelectorUpdateOnBackground->isChecked()); cfg.writeEntry("hidePopupOnClickCheck", ui->hidePopupOnClickCheck->isChecked()); + cfg.writeEntry("useCustomColorForBackground", ui->useCustomColorForBackground->isChecked()); + + cfg.writeEntry("customSelectorBackgroundColor", ui->customColorBackgroundSelector->color().toQColor()); //mypaint model int shadeMyPaintComboBoxIndex = ui->ACSshadeSelectorMyPaintColorModelComboBox->currentIndex(); if (shadeMyPaintComboBoxIndex == 0 ) { cfg.writeEntry("shadeMyPaintType", "HSV"); } else if (shadeMyPaintComboBoxIndex == 1 ) { cfg.writeEntry("shadeMyPaintType", "HSL"); } else if (shadeMyPaintComboBoxIndex == 2 ) { cfg.writeEntry("shadeMyPaintType", "HSI"); } else { // HSY cfg.writeEntry("shadeMyPaintType", "HSY"); } cfg.writeEntry("minimalShadeSelectorAsGradient", ui->minimalShadeSelectorAsGradient->isChecked()); cfg.writeEntry("minimalShadeSelectorPatchCount", ui->minimalShadeSelectorPatchesPerLine->value()); cfg.writeEntry("minimalShadeSelectorLineConfig", ui->minimalShadeSelectorLineSettings->toString()); cfg.writeEntry("minimalShadeSelectorLineHeight", ui->minimalShadeSelectorLineHeight->value()); //color selector KisColorSelectorComboBox* cstw = dynamic_cast(ui->colorSelectorConfiguration); cfg.writeEntry("colorSelectorConfiguration", cstw->configuration().toString()); cfg.writeEntry("hsxSettingType", ui->colorSelectorTypeComboBox->currentIndex()); //luma// cfg.writeEntry("lumaR", ui->l_lumaR->value()); cfg.writeEntry("lumaG", ui->l_lumaG->value()); cfg.writeEntry("lumaB", ui->l_lumaB->value()); cfg.writeEntry("gamma", ui->SP_Gamma->value()); //slider// hsxcfg.writeEntry("hsvH", ui->csl_hsvH->isChecked()); hsxcfg.writeEntry("hsvS", ui->csl_hsvS->isChecked()); hsxcfg.writeEntry("hsvV", ui->csl_hsvV->isChecked()); hsxcfg.writeEntry("hslH", ui->csl_hslH->isChecked()); hsxcfg.writeEntry("hslS", ui->csl_hslS->isChecked()); hsxcfg.writeEntry("hslL", ui->csl_hslL->isChecked()); hsxcfg.writeEntry("hsiH", ui->csl_hsiH->isChecked()); hsxcfg.writeEntry("hsiS", ui->csl_hsiS->isChecked()); hsxcfg.writeEntry("hsiI", ui->csl_hsiI->isChecked()); hsxcfg.writeEntry("hsyH", ui->csl_hsyH->isChecked()); hsxcfg.writeEntry("hsyS", ui->csl_hsyS->isChecked()); hsxcfg.writeEntry("hsyY", ui->csl_hsyY->isChecked()); //hotkeys// hotkeycfg.writeEntry("steps_lightness", ui->sb_lightness->value()); hotkeycfg.writeEntry("steps_saturation", ui->sb_saturation->value()); hotkeycfg.writeEntry("steps_hue", ui->sb_hue->value()); hotkeycfg.writeEntry("steps_redgreen", ui->sb_rg->value()); hotkeycfg.writeEntry("steps_blueyellow", ui->sb_by->value()); emit settingsChanged(); } //void KisColorSelectorSettings::changeEvent(QEvent *e) //{ // QDialog::changeEvent(e); // switch (e->type()) { // case QEvent::LanguageChange: // ui->retranslateUi(this); // break; // default: // break; // } //} void KisColorSelectorSettings::changedColorDocker(int index) { // having a situation where too many sections are visible makes the window too large. turn all off before turning more on ui->colorSliderOptions->hide(); ui->advancedColorSelectorOptions->hide(); ui->hotKeyOptions->hide(); if (index == 0) { // advanced color selector options selected ui->advancedColorSelectorOptions->show(); ui->colorSliderOptions->hide(); ui->hotKeyOptions->hide(); } // else if (index == 1) { // color slider options selected // ui->advancedColorSelectorOptions->hide(); // ui->hotKeyOptions->hide(); // ui->colorSliderOptions->show(); // } else { ui->colorSliderOptions->hide(); ui->advancedColorSelectorOptions->hide(); ui->hotKeyOptions->show(); } } void KisColorSelectorSettings::changedACSColorSelectorType(int index) { ui->lumaCoefficientGroupbox->setVisible(false); if (index == 0) { // HSV ui->ACSTypeDescriptionLabel->setText(i18n("Values goes from black to white, or black to the most saturated color. Saturation, in turn, goes from the most saturated color to white, gray or black.")); } else if (index == 1) { // HSL ui->ACSTypeDescriptionLabel->setText(i18n("Lightness goes from black to white, with middle gray being equal to the most saturated color.")); } else if (index == 2) { // HSI ui->ACSTypeDescriptionLabel->setText(i18n("Intensity maps to the sum of rgb components")); } else { // HSY' ui->ACSTypeDescriptionLabel->setText(i18n("Luma(Y') is weighted by its coefficients which are configurable. Default values are set to 'rec 709'.")); ui->lumaCoefficientGroupbox->setVisible(true); } ui->colorSelectorConfiguration->update(); emit hsxchanged(index); } void KisColorSelectorSettings::changedACSColorAlignment(bool toggled) { // this slot is tied to the horizontal radio button's state being changed // you can infer the vertical state ui->lbl_commonColorsNumCols->setDisabled(toggled); ui->commonColorsNumCols->setDisabled(toggled); ui->lbl_commonColorsNumRows->setEnabled(toggled); ui->commonColorsNumRows->setEnabled(toggled); } void KisColorSelectorSettings::changedACSLastUsedColorAlignment(bool toggled) { // this slot is tied to the horizontal radio button's state being changed // you can infer the vertical state ui->lbl_lastUsedNumCols->setDisabled(toggled); ui->lastUsedColorsNumCols->setDisabled(toggled); ui->lbl_lastUsedNumRows->setEnabled(toggled); ui->lastUsedColorsNumRows->setEnabled(toggled); } void KisColorSelectorSettings::changedACSShadeSelectorType(int index) { if (index == 0) { // MyPaint ui->minimalShadeSelectorGroup->hide(); ui->myPaintColorModelLabel->show(); ui->ACSshadeSelectorMyPaintColorModelComboBox->show(); } else if (index == 1) { // Minimal ui->minimalShadeSelectorGroup->show(); ui->myPaintColorModelLabel->hide(); ui->ACSshadeSelectorMyPaintColorModelComboBox->hide(); }else { // do not show ui->minimalShadeSelectorGroup->hide(); ui->myPaintColorModelLabel->hide(); ui->ACSshadeSelectorMyPaintColorModelComboBox->hide(); } } void KisColorSelectorSettings::useDifferentColorSpaceChecked(bool enabled) { ui->colorSpace->setEnabled(enabled); } +void KisColorSelectorSettings::useCustomColorForSelector(bool enabled) +{ + ui->customColorBackgroundSelector->setEnabled(enabled); +} + void KisColorSelectorSettings::loadPreferences() { //read cfg //don't forget to also add a new entry to the default preferences KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); KConfigGroup hsxcfg = KSharedConfig::openConfig()->group("hsxColorSlider"); KConfigGroup hotkeycfg = KSharedConfig::openConfig()->group("colorhotkeys"); // Advanced color selector ui->dockerResizeOptionsComboBox->setCurrentIndex( (int)cfg.readEntry("onDockerResize", 0) ); ui->zoomSelectorOptionComboBox->setCurrentIndex( (int) cfg.readEntry("zoomSelectorOptions", 0) ); ui->popupSize->setValue(cfg.readEntry("zoomSize", 280)); ui->chkShowColorSelector->setChecked((bool) cfg.readEntry("showColorSelector", true)); { KisConfig kisconfig(true); const KoColorSpace *cs = kisconfig.customColorSelectorColorSpace(); if (cs) { ui->useDifferentColorSpaceCheckbox->setChecked(true); ui->colorSpace->setEnabled(true); ui->colorSpace->setCurrentColorSpace(cs); } else { ui->useDifferentColorSpaceCheckbox->setChecked(false); ui->colorSpace->setEnabled(false); } } //color patches ui->lastUsedColorsShow->setChecked(cfg.readEntry("lastUsedColorsShow", true)); bool a = cfg.readEntry("lastUsedColorsAlignment", true); ui->lastUsedColorsAlignVertical->setChecked(a); ui->lastUsedColorsAlignHorizontal->setChecked(!a); ui->lastUsedColorsAllowScrolling->setChecked(cfg.readEntry("lastUsedColorsScrolling", true)); ui->lastUsedColorsNumCols->setValue(cfg.readEntry("lastUsedColorsNumCols", 1)); ui->lastUsedColorsNumRows->setValue(cfg.readEntry("lastUsedColorsNumRows", 1)); ui->lastUsedColorsPatchCount->setValue(cfg.readEntry("lastUsedColorsCount", 20)); ui->lastUsedColorsWidth->setValue(cfg.readEntry("lastUsedColorsWidth", 16)); ui->lastUsedColorsHeight->setValue(cfg.readEntry("lastUsedColorsHeight", 16)); ui->commonColorsShow->setChecked(cfg.readEntry("commonColorsShow", true)); a = cfg.readEntry("commonColorsAlignment", false); ui->commonColorsAlignVertical->setChecked(a); ui->commonColorsAlignHorizontal->setChecked(!a); ui->commonColorsAllowScrolling->setChecked(cfg.readEntry("commonColorsScrolling", true)); ui->commonColorsNumCols->setValue(cfg.readEntry("commonColorsNumCols", 1)); ui->commonColorsNumRows->setValue(cfg.readEntry("commonColorsNumRows", 1)); ui->commonColorsPatchCount->setValue(cfg.readEntry("commonColorsCount", 12)); ui->commonColorsWidth->setValue(cfg.readEntry("commonColorsWidth", 16)); ui->commonColorsHeight->setValue(cfg.readEntry("commonColorsHeight", 16)); ui->commonColorsAutoUpdate->setChecked(cfg.readEntry("commonColorsAutoUpdate", false)); //shade selector QString shadeSelectorType=cfg.readEntry("shadeSelectorType", "Minimal"); if ( shadeSelectorType == "MyPaint") { ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(0); } else if (shadeSelectorType == "Minimal") { ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(1); } else { // Hidden ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(2); } ui->shadeSelectorUpdateOnRightClick->setChecked(cfg.readEntry("shadeSelectorUpdateOnRightClick", false)); ui->shadeSelectorUpdateOnLeftClick->setChecked(cfg.readEntry("shadeSelectorUpdateOnLeftClick", false)); ui->shadeSelectorUpdateOnForeground->setChecked(cfg.readEntry("shadeSelectorUpdateOnForeground", true)); ui->shadeSelectorUpdateOnBackground->setChecked(cfg.readEntry("shadeSelectorUpdateOnBackground", true)); ui->hidePopupOnClickCheck->setChecked(cfg.readEntry("hidePopupOnClickCheck", false)); + ui->useCustomColorForBackground->setChecked(cfg.readEntry("useCustomColorForBackground", false)); + connect(ui->useCustomColorForBackground, SIGNAL(clicked(bool)), this, SLOT(useCustomColorForSelector(bool))); + + + QColor storedColor = cfg.readEntry("customSelectorBackgroundColor", QColor(Qt::gray)); + KoColor c; + c.fromQColor(storedColor); + ui->customColorBackgroundSelector->setColor(c); + + ui->customColorBackgroundSelector->setEnabled(cfg.readEntry("useCustomColorForBackground", false)); QString shadeMyPaintType = cfg.readEntry("shadeMyPaintType", "HSV"); if (shadeMyPaintType == "HSV" ) { ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(0); } else if (shadeMyPaintType == "HSL" ) { ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(1); } else if (shadeMyPaintType == "HSI" ) { ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(2); } else { // HSY ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(3); } bool asGradient = cfg.readEntry("minimalShadeSelectorAsGradient", true); if(asGradient) ui->minimalShadeSelectorAsGradient->setChecked(true); else ui->minimalShadeSelectorAsColorPatches->setChecked(true); ui->minimalShadeSelectorPatchesPerLine->setValue(cfg.readEntry("minimalShadeSelectorPatchCount", 10)); ui->minimalShadeSelectorLineSettings->fromString(cfg.readEntry("minimalShadeSelectorLineConfig", "0|0.2|0|0|0|0|0;1|0|1|1|0|0|0;2|0|-1|1|0|0|0;")); ui->minimalShadeSelectorLineHeight->setValue(cfg.readEntry("minimalShadeSelectorLineHeight", 10)); int hsxSettingType= (int)cfg.readEntry("hsxSettingType", 0); ui->colorSelectorTypeComboBox->setCurrentIndex(hsxSettingType); //color selector KisColorSelectorComboBox* cstw = dynamic_cast(ui->colorSelectorConfiguration); cstw->setConfiguration(KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", "3|0|5|0"))); // triangle selector //luma values// ui->l_lumaR->setValue(cfg.readEntry("lumaR", 0.2126)); ui->l_lumaG->setValue(cfg.readEntry("lumaG", 0.7152)); ui->l_lumaB->setValue(cfg.readEntry("lumaB", 0.0722)); ui->SP_Gamma->setValue(cfg.readEntry("gamma", 2.2)); //color sliders// ui->csl_hsvH->setChecked(hsxcfg.readEntry("hsvH", false)); ui->csl_hsvS->setChecked(hsxcfg.readEntry("hsvS", false)); ui->csl_hsvV->setChecked(hsxcfg.readEntry("hsvV", false)); ui->csl_hslH->setChecked(hsxcfg.readEntry("hslH", true)); ui->csl_hslS->setChecked(hsxcfg.readEntry("hslS", true)); ui->csl_hslL->setChecked(hsxcfg.readEntry("hslL", true)); ui->csl_hsiH->setChecked(hsxcfg.readEntry("hsiH", false)); ui->csl_hsiS->setChecked(hsxcfg.readEntry("hsiS", false)); ui->csl_hsiI->setChecked(hsxcfg.readEntry("hsiI", false)); ui->csl_hsyH->setChecked(hsxcfg.readEntry("hsyH", false)); ui->csl_hsyS->setChecked(hsxcfg.readEntry("hsyS", false)); ui->csl_hsyY->setChecked(hsxcfg.readEntry("hsyY", false)); //hotkeys// ui->sb_lightness->setValue(hotkeycfg.readEntry("steps_lightness", 10)); ui->sb_saturation->setValue(hotkeycfg.readEntry("steps_saturation", 10)); ui->sb_hue->setValue(hotkeycfg.readEntry("steps_hue", 36)); ui->sb_rg->setValue(hotkeycfg.readEntry("steps_redgreen", 10)); ui->sb_by->setValue(hotkeycfg.readEntry("steps_blueyellow", 10)); } void KisColorSelectorSettings::loadDefaultPreferences() { //set defaults //if you change something, don't forget that loadPreferences should be kept in sync // advanced color selector docker ui->dockerResizeOptionsComboBox->setCurrentIndex(0); ui->zoomSelectorOptionComboBox->setCurrentIndex(0); ui->popupSize->setValue(280); ui->chkShowColorSelector->setChecked(true); ui->useDifferentColorSpaceCheckbox->setChecked(false); ui->colorSpace->setCurrentColorModel(KoID("RGBA")); ui->colorSpace->setCurrentColorDepth(KoID("U8")); ui->colorSpace->setCurrentProfile(KoColorSpaceRegistry::instance()->rgb8()->profile()->name()); //color patches ui->lastUsedColorsShow->setChecked(true); ui->lastUsedColorsAlignVertical->setChecked(true); ui->lastUsedColorsAlignHorizontal->setChecked(false); ui->lastUsedColorsAllowScrolling->setChecked(true); ui->lastUsedColorsNumCols->setValue(1); ui->lastUsedColorsNumRows->setValue(1); ui->lastUsedColorsPatchCount->setValue(20); ui->lastUsedColorsWidth->setValue(16); ui->lastUsedColorsHeight->setValue(16); ui->commonColorsShow->setChecked(true); ui->commonColorsAlignVertical->setChecked(false); ui->commonColorsAlignHorizontal->setChecked(true); ui->commonColorsAllowScrolling->setChecked(true); ui->commonColorsNumCols->setValue(1); ui->commonColorsNumRows->setValue(1); ui->commonColorsPatchCount->setValue(12); ui->commonColorsWidth->setValue(16); ui->commonColorsHeight->setValue(16); ui->commonColorsAutoUpdate->setChecked(false); //shade selector ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(1); // Minimal ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(0); ui->shadeSelectorUpdateOnRightClick->setChecked(false); ui->shadeSelectorUpdateOnLeftClick->setChecked(false); ui->shadeSelectorUpdateOnForeground->setChecked(true); ui->shadeSelectorUpdateOnBackground->setChecked(true); bool asGradient = true; if(asGradient) ui->minimalShadeSelectorAsGradient->setChecked(true); else ui->minimalShadeSelectorAsColorPatches->setChecked(true); ui->minimalShadeSelectorPatchesPerLine->setValue(10); ui->minimalShadeSelectorLineSettings->fromString("0|0.2|0|0|0|0|0;1|0|1|1|0|0|0;2|0|-1|1|0|0|0;"); ui->minimalShadeSelectorLineHeight->setValue(10); // set advanced color selector to use HSV ui->colorSelectorTypeComboBox->setCurrentIndex(0); KisColorSelectorComboBox* cstw = dynamic_cast(ui->colorSelectorConfiguration); cstw->setConfiguration(KisColorSelectorConfiguration("3|0|5|0")); // triangle selector //luma// ui->l_lumaR->setValue(0.2126); ui->l_lumaG->setValue(0.7152); ui->l_lumaB->setValue(0.0722); ui->SP_Gamma->setValue(2.2); //color sliders// ui->csl_hsvH->setChecked(false); ui->csl_hsvS->setChecked(false); ui->csl_hsvV->setChecked(false); ui->csl_hslH->setChecked(true); ui->csl_hslS->setChecked(true); ui->csl_hslL->setChecked(true); ui->csl_hsiH->setChecked(false); ui->csl_hsiS->setChecked(false); ui->csl_hsiI->setChecked(false); ui->csl_hsyH->setChecked(false); ui->csl_hsyS->setChecked(false); ui->csl_hsyY->setChecked(false); //hotkeys// ui->sb_lightness->setValue(10); ui->sb_saturation->setValue(10); ui->sb_hue->setValue(36); ui->sb_rg->setValue(10); ui->sb_by->setValue(10); } KisColorSelectorSettingsDialog::KisColorSelectorSettingsDialog(QWidget *parent) : QDialog(parent), m_widget(new KisColorSelectorSettings(this)) { QLayout* l = new QVBoxLayout(this); l->addWidget(m_widget); m_widget->loadPreferences(); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::RestoreDefaults, Qt::Horizontal, this); l->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), m_widget, SLOT(savePreferences())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked()), m_widget, SLOT(loadDefaultPreferences())); } diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_settings.h b/plugins/dockers/advancedcolorselector/kis_color_selector_settings.h index 499eb8a658..2af1897365 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_settings.h +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_settings.h @@ -1,95 +1,96 @@ /* * Copyright (C) 2010 Celarek Adam * * 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_COLOR_SELECTOR_SETTINGS_H #define KIS_COLOR_SELECTOR_SETTINGS_H #include #include "kis_preference_set_registry.h" namespace Ui { class KisColorSelectorSettings; } class QIcon; class KisColorSelectorSettings : public KisPreferenceSet { Q_OBJECT public: KisColorSelectorSettings(QWidget *parent = 0); ~KisColorSelectorSettings() override; QString id() override; QString name() override; QString header() override; QIcon icon() override; public Q_SLOTS: void savePreferences() const override; void loadPreferences() override; void loadDefaultPreferences() override; void changedColorDocker(int); void useDifferentColorSpaceChecked(bool); + void useCustomColorForSelector(bool); void changedACSColorSelectorType(int); void changedACSShadeSelectorType(int); void changedACSColorAlignment(bool); void changedACSLastUsedColorAlignment(bool); Q_SIGNALS: void settingsChanged() const; void hsxchanged(int); protected: //void changeEvent(QEvent *e); private: Ui::KisColorSelectorSettings *ui; }; class KisColorSelectorSettingsUpdateRepeater : public QObject { Q_OBJECT Q_SIGNALS: void settingsUpdated(); public Q_SLOTS: void updateSettings() { emit settingsUpdated(); } }; class KisColorSelectorSettingsFactory : public KisAbstractPreferenceSetFactory { public: KisPreferenceSet* createPreferenceSet() override { KisColorSelectorSettings* ps = new KisColorSelectorSettings(); QObject::connect(ps, SIGNAL(settingsChanged()), &repeater, SLOT(updateSettings()), Qt::UniqueConnection); return ps; } QString id() const override { return "ColorSelectorSettings"; } KisColorSelectorSettingsUpdateRepeater repeater; }; class KisColorSelectorSettingsDialog : public QDialog { Q_OBJECT public: KisColorSelectorSettingsDialog(QWidget *parent = 0); private: KisColorSelectorSettings* m_widget; }; #endif // KIS_COLOR_SELECTOR_SETTINGS_H diff --git a/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.cpp b/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.cpp index b33c736e6a..602b37a01a 100644 --- a/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.cpp +++ b/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.cpp @@ -1,183 +1,190 @@ /* * Copyright (c) 2010 Adam Celarek * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_minimal_shade_selector.h" #include #include #include #include +#include #include #include #include #include "KoCanvasResourceProvider.h" #include "kis_shade_selector_line.h" #include "kis_color_selector_base_proxy.h" KisMinimalShadeSelector::KisMinimalShadeSelector(QWidget *parent) : KisColorSelectorBase(parent) , m_canvas(0) , m_proxy(new KisColorSelectorBaseProxyObject(this)) { setAcceptDrops(true); QVBoxLayout* l = new QVBoxLayout(this); l->setSpacing(0); l->setMargin(0); updateSettings(); setMouseTracking(true); } KisMinimalShadeSelector::~KisMinimalShadeSelector() { } void KisMinimalShadeSelector::unsetCanvas() { KisColorSelectorBase::unsetCanvas(); m_canvas = 0; } void KisMinimalShadeSelector::setCanvas(KisCanvas2 *canvas) { KisColorSelectorBase::setCanvas(canvas); m_canvas = canvas; } void KisMinimalShadeSelector::setColor(const KoColor& color) { m_lastRealColor = color; for(int i=0; isetColor(color); } } void KisMinimalShadeSelector::updateSettings() { KisColorSelectorBase::updateSettings(); KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); QString stri = cfg.readEntry("minimalShadeSelectorLineConfig", "0|0.2|0|0"); QStringList strili = stri.split(';', QString::SkipEmptyParts); int lineCount = strili.size(); while(lineCount-m_shadingLines.size() > 0) { KisShadeSelectorLine *line = new KisShadeSelectorLine(m_proxy.data(), this); m_shadingLines.append(line); m_shadingLines.last()->setLineNumber(m_shadingLines.size()-1); layout()->addWidget(m_shadingLines.last()); } while(lineCount-m_shadingLines.size() < 0) { layout()->removeWidget(m_shadingLines.last()); delete m_shadingLines.takeLast(); } for(int i=0; ifromString(strili.at(i)); } int lineHeight = cfg.readEntry("minimalShadeSelectorLineHeight", 20); setMinimumHeight(lineCount*lineHeight+2*lineCount); setMaximumHeight(lineCount*lineHeight+2*lineCount); for(int i=0; iupdateSettings(); setPopupBehaviour(false, false); } void KisMinimalShadeSelector::mousePressEvent(QMouseEvent * e) { Q_FOREACH (KisShadeSelectorLine* line, m_shadingLines) { QMouseEvent newEvent(e->type(), line->mapFromGlobal(e->globalPos()), e->button(), e->buttons(), e->modifiers()); if(line->rect().contains(newEvent.pos())) line->mousePressEvent(&newEvent); } KisColorSelectorBase::mousePressEvent(e); } void KisMinimalShadeSelector::mouseMoveEvent(QMouseEvent * e) { Q_FOREACH (KisShadeSelectorLine* line, m_shadingLines) { QMouseEvent newEvent(e->type(), line->mapFromGlobal(e->globalPos()), e->button(), e->buttons(), e->modifiers()); if(line->rect().contains(newEvent.pos())) line->mouseMoveEvent(&newEvent); } KisColorSelectorBase::mouseMoveEvent(e); } void KisMinimalShadeSelector::mouseReleaseEvent(QMouseEvent * e) { Q_FOREACH (KisShadeSelectorLine* line, m_shadingLines) { QMouseEvent newEvent(e->type(), line->mapFromGlobal(e->globalPos()), e->button(), e->buttons(), e->modifiers()); if(line->rect().contains(newEvent.pos())) line->mouseReleaseEvent(&newEvent); } KisColorSelectorBase::mouseReleaseEvent(e); } void KisMinimalShadeSelector::canvasResourceChanged(int key, const QVariant &v) { if(m_colorUpdateAllowed==false) return; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); bool onForeground = cfg.readEntry("shadeSelectorUpdateOnForeground", false); bool onBackground = cfg.readEntry("shadeSelectorUpdateOnBackground", true); if ((key == KoCanvasResourceProvider::ForegroundColor && onForeground) || (key == KoCanvasResourceProvider::BackgroundColor && onBackground)) { setColor(v.value()); } } void KisMinimalShadeSelector::paintEvent(QPaintEvent *) { - QPainter painter(this); - painter.fillRect(0,0,width(), height(), QColor(128,128,128)); + QPainter p(this); + KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); + + if (cfg.readEntry("useCustomColorForBackground", false)) { + p.fillRect(0,0,width(), height(), cfg.readEntry("customSelectorBackgroundColor", QColor(Qt::gray))); + } else { + p.fillRect(0,0,width(), height(), qApp->palette().window().color()); + } } KisColorSelectorBase* KisMinimalShadeSelector::createPopup() const { KisMinimalShadeSelector* popup = new KisMinimalShadeSelector(0); popup->setColor(m_lastRealColor); return popup; } diff --git a/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box_popup.cpp b/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box_popup.cpp index 5920fef600..af4b003019 100644 --- a/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box_popup.cpp +++ b/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box_popup.cpp @@ -1,171 +1,182 @@ /* * Copyright (c) 2010 Adam Celarek * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shade_selector_line_combo_box.h" #include "kis_shade_selector_line_combo_box_popup.h" #include #include #include +#include +#include +#include +#include #include "kis_global.h" #include "kis_shade_selector_line.h" #include "kis_shade_selector_line_editor.h" #include "kis_color_selector_base_proxy.h" KisShadeSelectorLineComboBoxPopup::KisShadeSelectorLineComboBoxPopup(QWidget* parent) : QWidget(parent, Qt::Popup), spacing(10), m_lastHighlightedItem(0), m_lastSelectedItem(0), m_lineEditor(0), m_parentProxy(new KisColorSelectorBaseProxyNoop()) { setMouseTracking(true); QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(spacing); layout->addWidget(new KisShadeSelectorLine(1.0, 0.0, 0.0, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.1, 0.0, 0.0, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.2, 0.0, 0.0, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, 0.5, 0.0, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, 1.0, 0.0, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, 0.0, 0.5, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, 0.0, 1.0, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, 0.5, 0.5, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, 1.0, 1.0, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, -0.5, 0.5, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, -1.0, 1.0, m_parentProxy.data(), this)); layout->addWidget(new KisShadeSelectorLine(0.0, 0.5, 0.5, m_parentProxy.data(), this, -0.04)); layout->addWidget(new KisShadeSelectorLine(0.0, 0.5, 0.5, m_parentProxy.data(), this, +0.04)); layout->addWidget(new KisShadeSelectorLine(0.0, -0.5, 0.5, m_parentProxy.data(), this, -0.04)); KisShadeSelectorLine* preview = new KisShadeSelectorLine(0.0, -0.5, 0.5, m_parentProxy.data(), this, +0.04); m_lineEditor = new KisShadeSelectorLineEditor(this, preview); layout->addWidget(preview); layout->addWidget(m_lineEditor); connect(m_lineEditor, SIGNAL(requestActivateLine(QWidget*)), SLOT(activateItem(QWidget*))); for(int i=0; ilayout()->count(); i++) { KisShadeSelectorLine* item = dynamic_cast(this->layout()->itemAt(i)->widget()); if(item!=0) { item->setMouseTracking(true); item->setEnabled(false); KoColor color; color.fromQColor(QColor(190, 50, 50)); item->setColor(color); item->showHelpText(); } } } KisShadeSelectorLineComboBoxPopup::~KisShadeSelectorLineComboBoxPopup() { } void KisShadeSelectorLineComboBoxPopup::setConfiguration(const QString &string) { m_lineEditor->fromString(string); } void KisShadeSelectorLineComboBoxPopup::updateSelectedArea(const QRect &newRect) { QRect oldSelectedArea = m_selectedArea; m_selectedArea = newRect; update(oldSelectedArea); update(m_selectedArea); } void KisShadeSelectorLineComboBoxPopup::updateHighlightedArea(const QRect &newRect) { QRect oldHighlightArea = m_highlightedArea; m_highlightedArea = newRect; update(oldHighlightArea); update(m_highlightedArea); } void KisShadeSelectorLineComboBoxPopup::activateItem(QWidget *widget) { KisShadeSelectorLineBase* item = dynamic_cast(widget); KIS_ASSERT_RECOVER_RETURN(item); QRect itemRect = kisGrowRect(item->geometry(), spacing / 2 - 1); m_lastSelectedItem = item; updateSelectedArea(itemRect); } void KisShadeSelectorLineComboBoxPopup::paintEvent(QPaintEvent *) { QPainter painter(this); - painter.fillRect(0,0,width(), height(), QColor(128,128,128)); + QPainter p(this); + KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); + + if (cfg.readEntry("useCustomColorForBackground", false)) { + p.fillRect(0,0,width(), height(), cfg.readEntry("customSelectorBackgroundColor", QColor(Qt::gray))); + } else { + p.fillRect(0,0,width(), height(), qApp->palette().window().color()); + } painter.fillRect(m_selectedArea, palette().highlight()); painter.setPen(QPen(palette().highlight(), 2)); painter.drawRect(m_highlightedArea); } void KisShadeSelectorLineComboBoxPopup::mouseMoveEvent(QMouseEvent * e) { if(rect().contains(e->pos())) { for(int i = 0; i < layout()->count(); i++) { KisShadeSelectorLineBase* item = dynamic_cast(layout()->itemAt(i)->widget()); KIS_ASSERT_RECOVER_RETURN(item); QRect itemRect = kisGrowRect(item->geometry(), spacing / 2 - 1); if(itemRect.contains(e->pos())) { m_lastHighlightedItem = item; updateHighlightedArea(itemRect); } } } else { updateHighlightedArea(QRect()); } } void KisShadeSelectorLineComboBoxPopup::mousePressEvent(QMouseEvent* e) { if(rect().contains(e->pos())) { mouseMoveEvent(e); m_lastSelectedItem = m_lastHighlightedItem; if (m_lastSelectedItem != m_lineEditor) { m_lineEditor->blockSignals(true); m_lineEditor->fromString(m_lastSelectedItem->toString()); m_lineEditor->blockSignals(false); } updateSelectedArea(m_highlightedArea); } if (m_lastSelectedItem) { KisShadeSelectorLineComboBox *parent = dynamic_cast(this->parent()); Q_ASSERT(parent); parent->setConfiguration(m_lastSelectedItem->toString()); } e->accept(); this->parentWidget()->update(); hide(); } diff --git a/plugins/dockers/advancedcolorselector/wdg_color_selector_settings.ui b/plugins/dockers/advancedcolorselector/wdg_color_selector_settings.ui index e0ffc90df6..29d33030b0 100644 --- a/plugins/dockers/advancedcolorselector/wdg_color_selector_settings.ui +++ b/plugins/dockers/advancedcolorselector/wdg_color_selector_settings.ui @@ -1,1572 +1,1596 @@ KisColorSelectorSettings 0 0 723 1108 0 0 Color Selector Settings Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 6 20 Docker: 0 0 0 30 0 0 0 280 0 Color Selector Show color selector true 0 0 Qt::Vertical QSizePolicy::MinimumExpanding 20 5 6 12 6 6 0 0 Qt::LeftToRight Color &Model Type: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter colorSelectorConfiguration 0 0 0 0 Type Description goes here Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true 0 0 Luma Coefficients 0 0 6 0 0 Red': Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 50 0 4 0 0 Green': 0 0 Blue': 0 0 50 0 4 0 0 50 0 4 <html><head/><body><p>This sets the gamma value that the linearised HSY Luminosity is crunched with. 1 makes the selector fully linear, 2.2 is a practical default value.</p></body></html> 1 -3.000000000000000 3.000000000000000 0.100000000000000 2.200000000000000 Gamma: + + + + + + + + Use custom color for selector background. + + + + + + + + + + + 0 0 Color Selector Uses Different Color Space than Ima&ge true false 0 0 Qt::Vertical QSizePolicy::MinimumExpanding 20 5 Behavior 0 0 Qt::LeftToRight When Docker Resizes: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Show Zoom Selector UI: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Zoom Selector Size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 px 100 1000 10 260 true 0 0 Hide Popup on click. Qt::Vertical QSizePolicy::MinimumExpanding 20 40 Shade Selector Type: Color model: Qt::Horizontal 20 20 0 0 0 Update Selector When: Right clicking on shade selector Left clicking on shade selector this doesn't include a color change by the shade selector Foreground color changes false this doesn't include a color change by the shade selector Background color change true 0 0 Minimal Shade Selector 0 0 Display: &Gradient Colo&r Patches 0 0 Qt::Horizontal 59 20 Line Count: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 10 3 Line Height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter px 8 99 16 Patches Per Line: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical QSizePolicy::MinimumExpanding 20 10 true 0 0 Color History 0 0 Show Color Histor&y true Patch Options Height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter px 16 Width: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter px 16 Max Patches: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 30 Qt::Horizontal 40 20 Layout 0 &Vertical true Colu&mns: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter lastUsedColorsNumCols 1 20 2 Hori&zontal &Rows: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter lastUsedColorsNumRows 1 20 3 Allow scrolling true Qt::Vertical QSizePolicy::MinimumExpanding 20 5 Colors from Image true Show Colors from the ima&ge true Patch Options Height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter px 16 Width: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter px 16 Max Patches: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 30 Qt::Horizontal 40 20 Layout 6 0 &Vertical Colu&mns: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter commonColorsNumCols 1 20 2 Hori&zontal true &Rows: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter commonColorsNumRows 1 20 3 this can be slow on big images Update after every stroke Allow scrolling true Qt::Vertical QSizePolicy::MinimumExpanding 20 10 HSV Sliders to Show Hue Saturation Value HSL Sliders to Show Hue Saturation Lightness HSI Sliders to Show Hue Saturation Intensity HSY' Sliders to Show Hue Saturation Luma Lightness, Saturation and Hue hotkey steps Lightness: steps Qt::Horizontal 40 20 Saturation: steps Qt::Horizontal 40 20 Hue: steps YUV Redder/Greener/Bluer/Yellower hotkey steps Redder/Greener: steps Qt::Horizontal 40 20 Bluer/Yellower: steps Qt::Vertical 20 10 + + KisColorButton + QPushButton +
kis_color_button.h
+
KisDoubleParseSpinBox QDoubleSpinBox
kis_double_parse_spin_box.h
KisColorSpaceSelector QWidget
widgets/kis_color_space_selector.h
1
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorSelectorComboBox QComboBox
kis_color_selector_combo_box.h
KisShadeSelectorLinesSettings QComboBox
kis_shade_selector_lines_settings.h
minimalShadeSelectorAsColorPatches toggled(bool) minimalShadeSelectorPatchesPerLine setEnabled(bool) 194 393 520 463
diff --git a/plugins/dockers/animation/CMakeLists.txt b/plugins/dockers/animation/CMakeLists.txt index a0b4b4beb4..890d72aa99 100644 --- a/plugins/dockers/animation/CMakeLists.txt +++ b/plugins/dockers/animation/CMakeLists.txt @@ -1,56 +1,55 @@ if (NOT WIN32 AND NOT APPLE) add_subdirectory(tests) endif() set(KRITA_ANIMATIONDOCKER_SOURCES animation_dockers.cpp - animation_docker.cpp timeline_docker.cpp onion_skins_docker.cpp timeline_layers_header.cpp timeline_ruler_header.cpp kis_time_based_item_model.cpp timeline_frames_model.cpp timeline_frames_view.cpp timeline_frames_item_delegate.cpp timeline_frames_index_converter.cpp timeline_node_list_keeper.cpp timeline_color_scheme.cpp timeline_insert_keyframe_dialog.cpp kis_draggable_tool_button.cpp kis_zoom_button.cpp kis_animation_utils.cpp kis_custom_modifiers_catcher.cpp kis_equalizer_column.cpp kis_equalizer_slider.cpp kis_equalizer_button.cpp kis_equalizer_widget.cpp kis_animation_curve_docker.cpp kis_animation_curves_model.cpp kis_animation_curves_view.cpp kis_animation_curves_value_ruler.cpp kis_animation_curves_keyframe_delegate.cpp kis_animation_curve_channel_list_model.cpp kis_animation_curve_channel_list_delegate.cpp ) ki18n_wrap_ui(KRITA_ANIMATIONDOCKER_SOURCES wdg_animation.ui onion_skins_docker.ui wdg_animation_curves.ui ) add_library(kritaanimationdocker MODULE ${KRITA_ANIMATIONDOCKER_SOURCES}) generate_export_header(kritaanimationdocker BASE_NAME kritaanimationdocker EXPORT_MACRO_NAME KRITAANIMATIONDOCKER_EXPORT) target_link_libraries(kritaanimationdocker kritaui kritawidgets) install(TARGETS kritaanimationdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/animation/animation_docker.cpp b/plugins/dockers/animation/animation_docker.cpp deleted file mode 100644 index 73583b99fc..0000000000 --- a/plugins/dockers/animation/animation_docker.cpp +++ /dev/null @@ -1,685 +0,0 @@ -/* - * 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 "animation_docker.h" - -#include "kis_global.h" - -#include "kis_canvas2.h" -#include "kis_image.h" -#include -#include -#include -#include "KisViewManager.h" -#include "kis_action_manager.h" -#include "kis_image_animation_interface.h" -#include "kis_animation_player.h" -#include "kis_time_range.h" -#include "kundo2command.h" -#include "kis_post_execution_undo_adapter.h" -#include "kis_keyframe_channel.h" -#include "kis_animation_utils.h" -#include "krita_utils.h" -#include "kis_image_config.h" -#include "kis_config.h" -#include "kis_signals_blocker.h" -#include "kis_node_manager.h" -#include "kis_transform_mask_params_factory_registry.h" - - -#include "ui_wdg_animation.h" - -void setupActionButton(const QString &text, - KisAction::ActivationFlags flags, - bool defaultValue, - QToolButton *button, - KisAction **action) -{ - *action = new KisAction(text, button); - (*action)->setActivationFlags(flags); - (*action)->setCheckable(true); - (*action)->setChecked(defaultValue); - button->setDefaultAction(*action); -} - -AnimationDocker::AnimationDocker() - : QDockWidget(i18n("Animation")) - , m_canvas(0) - , m_animationWidget(new Ui_WdgAnimation) - , m_mainWindow(0) -{ - QWidget* mainWidget = new QWidget(this); - setWidget(mainWidget); - - m_animationWidget->setupUi(mainWidget); -} - -AnimationDocker::~AnimationDocker() -{ - delete m_animationWidget; -} - -void AnimationDocker::setCanvas(KoCanvasBase * canvas) -{ - if(m_canvas == canvas) - return; - - setEnabled(canvas != 0); - - if (m_canvas) { - m_canvas->disconnectCanvasObserver(this); - m_canvas->image()->disconnect(this); - m_canvas->image()->animationInterface()->disconnect(this); - m_canvas->animationPlayer()->disconnect(this); - m_canvas->viewManager()->nodeManager()->disconnect(this); - } - - m_canvas = dynamic_cast(canvas); - - if (m_canvas && m_canvas->image()) { - - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - { - KisSignalsBlocker bloker(m_animationWidget->spinFromFrame, - m_animationWidget->spinToFrame, - m_animationWidget->intFramerate); - - m_animationWidget->spinFromFrame->setValue(animation->fullClipRange().start()); - m_animationWidget->spinToFrame->setValue(animation->fullClipRange().end()); - m_animationWidget->intFramerate->setValue(animation->framerate()); - } - - - connect(animation, SIGNAL(sigUiTimeChanged(int)), this, SLOT(slotGlobalTimeChanged())); - connect(animation, SIGNAL(sigFramerateChanged()), this, SLOT(slotFrameRateChanged())); - - - connect(m_canvas->animationPlayer(), SIGNAL(sigFrameChanged()), this, SLOT(slotGlobalTimeChanged())); - connect(m_canvas->animationPlayer(), SIGNAL(sigPlaybackStopped()), this, SLOT(slotGlobalTimeChanged())); - connect(m_canvas->animationPlayer(), SIGNAL(sigPlaybackStopped()), this, SLOT(updatePlayPauseIcon())); - connect(m_canvas->animationPlayer(), SIGNAL(sigPlaybackStarted()), this, SLOT(updatePlayPauseIcon())); - connect(m_canvas->animationPlayer(), SIGNAL(sigPlaybackStatisticsUpdated()), this, SLOT(updateDropFramesIcon())); - connect(m_animationWidget->doublePlaySpeed, - SIGNAL(valueChanged(double)), - m_canvas->animationPlayer(), - SLOT(slotUpdatePlaybackSpeed(double))); - - - connect(m_canvas->viewManager()->nodeManager(), SIGNAL(sigNodeActivated(KisNodeSP)), - this, SLOT(slotCurrentNodeChanged(KisNodeSP))); - - connect (animation, SIGNAL(sigFullClipRangeChanged()), this, SLOT(updateClipRange())); - - - - slotGlobalTimeChanged(); - slotCurrentNodeChanged(m_canvas->viewManager()->nodeManager()->activeNode()); - } - - slotUpdateIcons(); -} - -void AnimationDocker::unsetCanvas() -{ - setCanvas(0); -} - -void AnimationDocker::setViewManager(KisViewManager *view) -{ - setActions(view->actionManager()); - - slotUpdateIcons(); - connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); - m_mainWindow = view->mainWindow(); -} - -void AnimationDocker::slotAddOpacityKeyframe() -{ - // remember current node's opacity and set it once we create a new opacity keyframe - KisNodeSP node = m_canvas->viewManager()->activeNode(); - KIS_SAFE_ASSERT_RECOVER_RETURN(node); - - addKeyframe(KisKeyframeChannel::Opacity.id(), false); -} - -void AnimationDocker::slotDeleteOpacityKeyframe() -{ - deleteKeyframe(KisKeyframeChannel::Opacity.id()); -} - -void AnimationDocker::slotAddTransformKeyframe() -{ - if (!m_canvas) return; - - KisTransformMask *mask = dynamic_cast(m_canvas->viewManager()->activeNode().data()); - if (!mask) return; - - const int time = m_canvas->image()->animationInterface()->currentTime(); - - KUndo2Command *command = new KUndo2Command(kundo2_i18n("Add transform keyframe")); - KisTransformMaskParamsFactoryRegistry::instance()->autoAddKeyframe(mask, time, KisTransformMaskParamsInterfaceSP(), command); - - command->redo(); - m_canvas->currentImage()->postExecutionUndoAdapter()->addCommand(toQShared(command)); -} - -void AnimationDocker::slotDeleteTransformKeyframe() -{ - deleteKeyframe(KisKeyframeChannel::TransformArguments.id()); -} - -void AnimationDocker::slotUIRangeChanged() -{ - if (!m_canvas || !m_canvas->image()) return; - - int fromTime = m_animationWidget->spinFromFrame->value(); - int toTime = m_animationWidget->spinToFrame->value(); - - m_canvas->image()->animationInterface()->setFullClipRange(KisTimeRange::fromTime(fromTime, toTime)); -} - -void AnimationDocker::slotUIFramerateChanged() -{ - if (!m_canvas || !m_canvas->image()) return; - - m_canvas->image()->animationInterface()->setFramerate(m_animationWidget->intFramerate->value()); -} - -void AnimationDocker::slotOnionSkinOptions() -{ - if (m_mainWindow) { - QDockWidget *docker = m_mainWindow->dockWidget("OnionSkinsDocker"); - if (docker) { - docker->setVisible(!docker->isVisible()); - } - } -} - -void AnimationDocker::slotGlobalTimeChanged() -{ - int time = m_canvas->animationPlayer()->isPlaying() ? - m_canvas->animationPlayer()->currentTime() : - m_canvas->image()->animationInterface()->currentUITime(); - - m_animationWidget->intCurrentTime->setValue(time); - - const int frameRate = m_canvas->image()->animationInterface()->framerate(); - const int msec = 1000 * time / frameRate; - - QTime realTime; - realTime = realTime.addMSecs(msec); - - QString realTimeString = realTime.toString("hh:mm:ss.zzz"); - m_animationWidget->intCurrentTime->setToolTip(realTimeString); -} - -void AnimationDocker::slotFrameRateChanged() -{ - if (!m_canvas || !m_canvas->image()) return; - - int fpsOnUI = m_animationWidget->intFramerate->value(); - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - - if (animation->framerate() != fpsOnUI) { - m_animationWidget->intFramerate->setValue(animation->framerate()); - } - -} - -void AnimationDocker::slotTimeSpinBoxChanged() -{ - if (!m_canvas || !m_canvas->image()) return; - - int newTime = m_animationWidget->intCurrentTime->value(); - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - - if (m_canvas->animationPlayer()->isPlaying() || - newTime == animation->currentUITime()) { - - return; - } - - animation->requestTimeSwitchWithUndo(newTime); -} - -void AnimationDocker::slotPreviousFrame() -{ - if (!m_canvas) return; - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - - int time = animation->currentUITime() - 1; - if (time >= 0) { - animation->requestTimeSwitchWithUndo(time); - } -} - -void AnimationDocker::slotNextFrame() -{ - if (!m_canvas) return; - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - - int time = animation->currentUITime() + 1; - animation->requestTimeSwitchWithUndo(time); -} - -void AnimationDocker::slotPreviousKeyFrame() -{ - if (!m_canvas) return; - - KisNodeSP node = m_canvas->viewManager()->activeNode(); - if (!node) return; - - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - int time = animation->currentUITime(); - - KisKeyframeChannel *content = - node->getKeyframeChannel(KisKeyframeChannel::Content.id()); - - if (!content) return; - - KisKeyframeSP dstKeyframe; - KisKeyframeSP keyframe = content->keyframeAt(time); - - if (!keyframe) { - dstKeyframe = content->activeKeyframeAt(time); - } else { - dstKeyframe = content->previousKeyframe(keyframe); - } - - if (dstKeyframe) { - animation->requestTimeSwitchWithUndo(dstKeyframe->time()); - } -} - -void AnimationDocker::slotNextKeyFrame() -{ - if (!m_canvas) return; - - KisNodeSP node = m_canvas->viewManager()->activeNode(); - if (!node) return; - - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - int time = animation->currentUITime(); - - KisKeyframeChannel *content = - node->getKeyframeChannel(KisKeyframeChannel::Content.id()); - - if (!content) return; - - KisKeyframeSP dstKeyframe; - KisKeyframeSP keyframe = content->activeKeyframeAt(time); - - if (keyframe) { - dstKeyframe = content->nextKeyframe(keyframe); - } - - if (dstKeyframe) { - animation->requestTimeSwitchWithUndo(dstKeyframe->time()); - } -} - - -void AnimationDocker::slotFirstFrame() -{ - if (!m_canvas) return; - - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - animation->requestTimeSwitchWithUndo(0); -} - -void AnimationDocker::slotLastFrame() -{ - if (!m_canvas) return; - - KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); - animation->requestTimeSwitchWithUndo(animation->totalLength() - 1); -} - - -void AnimationDocker::slotPlayPause() -{ - if (!m_canvas) return; - - if (m_canvas->animationPlayer()->isPlaying()) { - m_canvas->animationPlayer()->stop(); - } else { - m_canvas->animationPlayer()->play(); - } - - updatePlayPauseIcon(); -} - -void AnimationDocker::updatePlayPauseIcon() -{ - bool isPlaying = m_canvas && m_canvas->animationPlayer() && m_canvas->animationPlayer()->isPlaying(); - - m_playPauseAction->setIcon(isPlaying ? - KisIconUtils::loadIcon("animation_stop") : - KisIconUtils::loadIcon("animation_play")); -} - -void AnimationDocker::updateLazyFrameIcon() -{ - KisImageConfig cfg(true); - - const bool value = cfg.lazyFrameCreationEnabled(); - - m_lazyFrameAction->setIcon(value ? - KisIconUtils::loadIcon("lazyframeOn") : - KisIconUtils::loadIcon("lazyframeOff")); - - m_lazyFrameAction->setText(QString("%1 (%2)") - .arg(KisAnimationUtils::lazyFrameCreationActionName) - .arg(KritaUtils::toLocalizedOnOff(value))); -} - -void AnimationDocker::updateDropFramesIcon() -{ - qreal effectiveFps = 0.0; - qreal realFps = 0.0; - qreal framesDropped = 0.0; - bool isPlaying = false; - - KisAnimationPlayer *player = - m_canvas && m_canvas->animationPlayer() ? - m_canvas->animationPlayer() : 0; - - if (player) { - effectiveFps = player->effectiveFps(); - realFps = player->realFps(); - framesDropped = player->framesDroppedPortion(); - isPlaying = player->isPlaying(); - } - - KisConfig cfg(true); - const bool value = cfg.animationDropFrames(); - - m_dropFramesAction->setIcon(value ? - KisIconUtils::loadIcon(framesDropped > 0.05 ? "droppedframes" : "dropframe") : - KisIconUtils::loadIcon("dropframe")); - - - QString text; - - if (!isPlaying) { - text = QString("%1 (%2)") - .arg(KisAnimationUtils::dropFramesActionName) - .arg(KritaUtils::toLocalizedOnOff(value)); - } else { - text = QString("%1 (%2)\n" - "%3\n" - "%4\n" - "%5") - .arg(KisAnimationUtils::dropFramesActionName) - .arg(KritaUtils::toLocalizedOnOff(value)) - .arg(i18n("Effective FPS:\t%1", effectiveFps)) - .arg(i18n("Real FPS:\t%1", realFps)) - .arg(i18n("Frames dropped:\t%1\%", framesDropped * 100)); - } - - m_dropFramesAction->setText(text); -} - -void AnimationDocker::slotUpdateIcons() -{ - m_previousFrameAction->setIcon(KisIconUtils::loadIcon("prevframe")); - m_nextFrameAction->setIcon(KisIconUtils::loadIcon("nextframe")); - - m_previousKeyFrameAction->setIcon(KisIconUtils::loadIcon("prevkeyframe")); - m_nextKeyFrameAction->setIcon(KisIconUtils::loadIcon("nextkeyframe")); - - m_firstFrameAction->setIcon(KisIconUtils::loadIcon("firstframe")); - m_lastFrameAction->setIcon(KisIconUtils::loadIcon("lastframe")); - - updatePlayPauseIcon(); - updateLazyFrameIcon(); - updateDropFramesIcon(); - - m_animationWidget->btnOnionSkinOptions->setIcon(KisIconUtils::loadIcon("onion_skin_options")); - m_animationWidget->btnOnionSkinOptions->setIconSize(QSize(22, 22)); - - - m_animationWidget->btnNextKeyFrame->setIconSize(QSize(22, 22)); - m_animationWidget->btnPreviousKeyFrame->setIconSize(QSize(22, 22)); - m_animationWidget->btnFirstFrame->setIconSize(QSize(22, 22)); - m_animationWidget->btnLastFrame->setIconSize(QSize(22, 22)); - - m_animationWidget->btnPreviousFrame->setIconSize(QSize(22, 22)); - m_animationWidget->btnPlay->setIconSize(QSize(22, 22)); - m_animationWidget->btnNextFrame->setIconSize(QSize(22, 22)); - m_animationWidget->btnAddKeyframe->setIconSize(QSize(22, 22)); - m_animationWidget->btnAddDuplicateFrame->setIconSize(QSize(22, 22)); - m_animationWidget->btnDeleteKeyframe->setIconSize(QSize(22, 22)); - m_animationWidget->btnLazyFrame->setIconSize(QSize(22, 22)); - m_animationWidget->btnDropFrames->setIconSize(QSize(22, 22)); -} - -void AnimationDocker::slotLazyFrameChanged(bool value) -{ - KisImageConfig cfg(false); - - if (value != cfg.lazyFrameCreationEnabled()) { - cfg.setLazyFrameCreationEnabled(value); - updateLazyFrameIcon(); - } -} - -void AnimationDocker::slotDropFramesChanged(bool value) -{ - KisConfig cfg(false); - - if (value != cfg.animationDropFrames()) { - cfg.setAnimationDropFrames(value); - updateDropFramesIcon(); - } -} - -void AnimationDocker::slotCurrentNodeChanged(KisNodeSP node) -{ - bool isNodeAnimatable = false; - - m_newKeyframeMenu->clear(); - m_deleteKeyframeMenu->clear(); - - if (!node.isNull()) { - if (KisAnimationUtils::supportsContentFrames(node)) { - isNodeAnimatable = true; - KisActionManager::safePopulateMenu(m_newKeyframeMenu, "add_blank_frame", m_actionManager); - KisActionManager::safePopulateMenu(m_deleteKeyframeMenu, "remove_frames", m_actionManager); - } - - if (node->inherits("KisLayer")) { - isNodeAnimatable = true; - m_newKeyframeMenu->addAction(m_addOpacityKeyframeAction); - m_deleteKeyframeMenu->addAction(m_deleteOpacityKeyframeAction); - } - - /* - if (node->inherits("KisTransformMask")) { - isNodeAnimatable = true; - m_newKeyframeMenu->addAction(m_addTransformKeyframeAction); - m_deleteKeyframeMenu->addAction(m_deleteTransformKeyframeAction); - } - */ - } - - m_animationWidget->btnAddKeyframe->setEnabled(isNodeAnimatable); - m_animationWidget->btnAddDuplicateFrame->setEnabled(isNodeAnimatable); - m_animationWidget->btnDeleteKeyframe->setEnabled(isNodeAnimatable); -} - -void AnimationDocker::updateClipRange() -{ - m_animationWidget->spinFromFrame->setValue(m_canvas->image()->animationInterface()->fullClipRange().start()); - m_animationWidget->spinToFrame->setValue(m_canvas->image()->animationInterface()->fullClipRange().end()); -} - -void AnimationDocker::addKeyframe(const QString &channel, bool copy) -{ - if (!m_canvas) return; - - KisNodeSP node = m_canvas->viewManager()->activeNode(); - if (!node) return; - - const int time = m_canvas->image()->animationInterface()->currentTime(); - KisAnimationUtils::createKeyframeLazy(m_canvas->image(), node, channel, time, copy); -} - -void AnimationDocker::deleteKeyframe(const QString &channel) -{ - if (!m_canvas) return; - - KisNodeSP node = m_canvas->viewManager()->activeNode(); - if (!node) return; - - const int time = m_canvas->image()->animationInterface()->currentTime(); - KisAnimationUtils::removeKeyframe(m_canvas->image(), node, channel, time); -} - -void AnimationDocker::setActions(KisActionManager *actionMan) -{ - m_actionManager = actionMan; - if (!m_actionManager) return; - - m_previousFrameAction = new KisAction(i18n("Previous Frame"), m_animationWidget->btnPreviousFrame); - m_previousFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); - m_animationWidget->btnPreviousFrame->setDefaultAction(m_previousFrameAction); - - m_nextFrameAction = new KisAction(i18n("Next Frame"), m_animationWidget->btnNextFrame); - m_nextFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); - m_animationWidget->btnNextFrame->setDefaultAction(m_nextFrameAction); - - m_previousKeyFrameAction = new KisAction(i18n("Previous Key Frame"), m_animationWidget->btnPreviousKeyFrame); - m_previousKeyFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); - m_animationWidget->btnPreviousKeyFrame->setDefaultAction(m_previousKeyFrameAction); - - m_nextKeyFrameAction = new KisAction(i18n("Next Key Frame"), m_animationWidget->btnNextKeyFrame); - m_nextKeyFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); - m_animationWidget->btnNextKeyFrame->setDefaultAction(m_nextKeyFrameAction); - - m_firstFrameAction = new KisAction(i18n("First Frame"), m_animationWidget->btnFirstFrame); - m_firstFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); - m_animationWidget->btnFirstFrame->setDefaultAction(m_firstFrameAction); - - m_lastFrameAction = new KisAction(i18n("Last Frame"), m_animationWidget->btnLastFrame); - m_lastFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); - m_animationWidget->btnLastFrame->setDefaultAction(m_lastFrameAction); - - - m_playPauseAction = new KisAction(i18n("Play / Stop"), m_animationWidget->btnPlay); - m_playPauseAction->setActivationFlags(KisAction::ACTIVE_IMAGE); - m_animationWidget->btnPlay->setDefaultAction(m_playPauseAction); - - KisAction *action = 0; - - action = m_actionManager->createAction("add_blank_frame"); - m_animationWidget->btnAddKeyframe->setDefaultAction(action); - - action = m_actionManager->createAction("add_duplicate_frame"); - m_animationWidget->btnAddDuplicateFrame->setDefaultAction(action); - - action = m_actionManager->createAction("remove_frames"); - m_animationWidget->btnDeleteKeyframe->setDefaultAction(action); - - m_newKeyframeMenu = new QMenu(this); - m_animationWidget->btnAddKeyframe->setMenu(m_newKeyframeMenu); - m_animationWidget->btnAddKeyframe->setPopupMode(QToolButton::MenuButtonPopup); - - m_deleteKeyframeMenu = new QMenu(this); - m_animationWidget->btnDeleteKeyframe->setMenu(m_deleteKeyframeMenu); - m_animationWidget->btnDeleteKeyframe->setPopupMode(QToolButton::MenuButtonPopup); - - - m_addOpacityKeyframeAction = m_actionManager->createAction("insert_opacity_keyframe"); - m_deleteOpacityKeyframeAction = m_actionManager->createAction("remove_opacity_keyframe"); - - - - m_addTransformKeyframeAction = new KisAction(KisAnimationUtils::addTransformKeyframeActionName, this); - m_deleteTransformKeyframeAction = new KisAction(KisAnimationUtils::removeTransformKeyframeActionName, this); - - - // other new stuff - m_actionManager->addAction("previous_frame", m_previousFrameAction); - m_actionManager->addAction("next_frame", m_nextFrameAction); - - m_actionManager->addAction("previous_keyframe", m_previousKeyFrameAction); - m_actionManager->addAction("next_keyframe", m_nextKeyFrameAction); - - m_actionManager->addAction("first_frame", m_firstFrameAction); - m_actionManager->addAction("last_frame", m_lastFrameAction); - - { - KisImageConfig cfg(true); - setupActionButton(KisAnimationUtils::lazyFrameCreationActionName, - KisAction::ACTIVE_IMAGE, - cfg.lazyFrameCreationEnabled(), - m_animationWidget->btnLazyFrame, - &m_lazyFrameAction); - } - - { - KisConfig cfg(true); - setupActionButton(KisAnimationUtils::dropFramesActionName, - KisAction::ACTIVE_IMAGE, - cfg.animationDropFrames(), - m_animationWidget->btnDropFrames, - &m_dropFramesAction); - } - - - // these actions are created in the setupActionButton() above, so we need to add actions after that - m_actionManager->addAction("lazy_frame", m_lazyFrameAction); - m_actionManager->addAction("drop_frames", m_dropFramesAction); - - m_actionManager->addAction("toggle_playback", m_playPauseAction); - - QFont font; - font.setPointSize(1.7 * font.pointSize()); - font.setBold(true); - m_animationWidget->intCurrentTime->setFont(font); - - connect(m_previousFrameAction, SIGNAL(triggered()), this, SLOT(slotPreviousFrame())); - connect(m_nextFrameAction, SIGNAL(triggered()), this, SLOT(slotNextFrame())); - - connect(m_previousKeyFrameAction, SIGNAL(triggered()), this, SLOT(slotPreviousKeyFrame())); - connect(m_nextKeyFrameAction, SIGNAL(triggered()), this, SLOT(slotNextKeyFrame())); - - connect(m_firstFrameAction, SIGNAL(triggered()), this, SLOT(slotFirstFrame())); - connect(m_lastFrameAction, SIGNAL(triggered()), this, SLOT(slotLastFrame())); - - connect(m_playPauseAction, SIGNAL(triggered()), this, SLOT(slotPlayPause())); - - connect(m_lazyFrameAction, SIGNAL(toggled(bool)), this, SLOT(slotLazyFrameChanged(bool))); - connect(m_dropFramesAction, SIGNAL(toggled(bool)), this, SLOT(slotDropFramesChanged(bool))); - - connect(m_addOpacityKeyframeAction, SIGNAL(triggered(bool)), this, SLOT(slotAddOpacityKeyframe())); - connect(m_deleteOpacityKeyframeAction, SIGNAL(triggered(bool)), this, SLOT(slotDeleteOpacityKeyframe())); - - connect(m_addTransformKeyframeAction, SIGNAL(triggered(bool)), this, SLOT(slotAddTransformKeyframe())); - connect(m_deleteTransformKeyframeAction, SIGNAL(triggered(bool)), this, SLOT(slotDeleteTransformKeyframe())); - - m_animationWidget->btnOnionSkinOptions->setToolTip(i18n("Onion Skins")); - connect(m_animationWidget->btnOnionSkinOptions, SIGNAL(clicked()), this, SLOT(slotOnionSkinOptions())); - - connect(m_animationWidget->spinFromFrame, SIGNAL(valueChanged(int)), this, SLOT(slotUIRangeChanged())); - connect(m_animationWidget->spinToFrame, SIGNAL(valueChanged(int)), this, SLOT(slotUIRangeChanged())); - connect(m_animationWidget->intFramerate, SIGNAL(valueChanged(int)), this, SLOT(slotUIFramerateChanged())); - - connect(m_animationWidget->intCurrentTime, SIGNAL(valueChanged(int)), SLOT(slotTimeSpinBoxChanged())); -} diff --git a/plugins/dockers/animation/animation_docker.h b/plugins/dockers/animation/animation_docker.h deleted file mode 100644 index fa70a90692..0000000000 --- a/plugins/dockers/animation/animation_docker.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2015 Jouni Pentikäinen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef _ANIMATION_DOCKER_H_ -#define _ANIMATION_DOCKER_H_ - -#include "kritaimage_export.h" - -#include -#include - -#include -#include -#include - -class Ui_WdgAnimation; -class KisMainWindow; - -class AnimationDocker : public QDockWidget, public KisMainwindowObserver { - Q_OBJECT -public: - AnimationDocker(); - ~AnimationDocker() override; - QString observerName() override { return "AnimationDocker"; } - void setCanvas(KoCanvasBase *canvas) override; - void unsetCanvas() override; - void setViewManager(KisViewManager *kisview) override; - -private Q_SLOTS: - void slotPreviousFrame(); - void slotNextFrame(); - - void slotPreviousKeyFrame(); - void slotNextKeyFrame(); - - void slotFirstFrame(); - void slotLastFrame(); - - void slotPlayPause(); - - void slotAddOpacityKeyframe(); - void slotDeleteOpacityKeyframe(); - - void slotAddTransformKeyframe(); - void slotDeleteTransformKeyframe(); - - void slotUIRangeChanged(); - void slotUIFramerateChanged(); - - void slotUpdateIcons(); - - void slotOnionSkinOptions(); - - void slotGlobalTimeChanged(); - void slotTimeSpinBoxChanged(); - - /** Update the frame rate UI field in the case it gets - * out of sync with the data model - */ - void slotFrameRateChanged(); - - void updatePlayPauseIcon(); - void updateLazyFrameIcon(); - void updateDropFramesIcon(); - - void slotLazyFrameChanged(bool value); - void slotDropFramesChanged(bool value); - - void slotCurrentNodeChanged(KisNodeSP node); - - void updateClipRange(); - -private: - - QPointer m_canvas; - QPointer m_actionManager; - Ui_WdgAnimation *m_animationWidget; - - KisAction *m_previousFrameAction; - KisAction *m_nextFrameAction; - - KisAction *m_previousKeyFrameAction; - KisAction *m_nextKeyFrameAction; - - KisAction *m_firstFrameAction; - KisAction *m_lastFrameAction; - - KisAction *m_playPauseAction; - - KisAction *m_lazyFrameAction; - KisAction *m_dropFramesAction; - - QMenu *m_newKeyframeMenu; - KisAction *m_addOpacityKeyframeAction; - KisAction *m_addTransformKeyframeAction; - QMenu *m_deleteKeyframeMenu; - KisAction *m_deleteOpacityKeyframeAction; - KisAction *m_deleteTransformKeyframeAction; - - KisMainWindow *m_mainWindow; - - void addKeyframe(const QString &channel, bool copy); - void deleteKeyframe(const QString &channel); - - void setActions(KisActionManager* actionManager); -}; - - -#endif diff --git a/plugins/dockers/animation/animation_dockers.cpp b/plugins/dockers/animation/animation_dockers.cpp index 037e778ef0..1d4d2d0255 100644 --- a/plugins/dockers/animation/animation_dockers.cpp +++ b/plugins/dockers/animation/animation_dockers.cpp @@ -1,172 +1,139 @@ /* * 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 "animation_dockers.h" -#include "animation_docker.h" #include "timeline_docker.h" #include "onion_skins_docker.h" #include "kis_animation_curve_docker.h" #include #include #include #include "KisViewManager.h" K_PLUGIN_FACTORY_WITH_JSON(AnimationDockersPluginFactory, "krita_animationdocker.json", registerPlugin();) -class AnimationDockerFactory : public KoDockFactoryBase { -public: - AnimationDockerFactory() - { - } - - QString id() const override - { - return QString( "AnimationDocker" ); - } - - virtual Qt::DockWidgetArea defaultDockWidgetArea() const - { - return Qt::RightDockWidgetArea; - } - - QDockWidget *createDockWidget() override - { - AnimationDocker *dockWidget = new AnimationDocker(); - dockWidget->setObjectName(id()); - - return dockWidget; - } - - DockPosition defaultDockPosition() const override - { - return DockMinimized; - } -private: -}; - class TimelineDockerFactory : public KoDockFactoryBase { public: TimelineDockerFactory() { } QString id() const override { - return QString( "TimelineDocker" ); + return QString( "AnimationTimelineDocker" ); } virtual Qt::DockWidgetArea defaultDockWidgetArea() const { return Qt::RightDockWidgetArea; } QDockWidget *createDockWidget() override { TimelineDocker *dockWidget = new TimelineDocker(); dockWidget->setObjectName(id()); return dockWidget; } DockPosition defaultDockPosition() const override { return DockMinimized; } private: }; class OnionSkinsDockerFactory : public KoDockFactoryBase { public: OnionSkinsDockerFactory() { } QString id() const override { return QString( "OnionSkinsDocker" ); } virtual Qt::DockWidgetArea defaultDockWidgetArea() const { return Qt::RightDockWidgetArea; } QDockWidget *createDockWidget() override { OnionSkinsDocker *dockWidget = new OnionSkinsDocker(); dockWidget->setObjectName(id()); return dockWidget; } DockPosition defaultDockPosition() const override { return DockMinimized; } private: }; class AnimationCurvesDockerFactory : public KoDockFactoryBase { public: AnimationCurvesDockerFactory() { } QString id() const override { return QString( "AnimationCurvesDocker" ); } virtual Qt::DockWidgetArea defaultDockWidgetArea() const { return Qt::RightDockWidgetArea; } QDockWidget *createDockWidget() override { KisAnimationCurveDocker *dockWidget = new KisAnimationCurveDocker(); dockWidget->setObjectName(id()); return dockWidget; } DockPosition defaultDockPosition() const override { return DockMinimized; } private: }; AnimationDockersPlugin::AnimationDockersPlugin(QObject *parent, const QVariantList &) : QObject(parent) { - KoDockRegistry::instance()->add(new AnimationDockerFactory()); KoDockRegistry::instance()->add(new TimelineDockerFactory()); KoDockRegistry::instance()->add(new OnionSkinsDockerFactory()); KoDockRegistry::instance()->add(new AnimationCurvesDockerFactory()); } AnimationDockersPlugin::~AnimationDockersPlugin() { m_view = 0; } #include "animation_dockers.moc" diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp index f07108ad01..a4318146dd 100644 --- a/plugins/dockers/animation/kis_time_based_item_model.cpp +++ b/plugins/dockers/animation/kis_time_based_item_model.cpp @@ -1,522 +1,532 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_time_based_item_model.h" #include #include #include "kis_animation_frame_cache.h" #include "kis_animation_player.h" #include "kis_signal_compressor_with_param.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_animation_utils.h" #include "kis_keyframe_channel.h" #include "kis_processing_applicator.h" #include "KisImageBarrierLockerWithFeedback.h" #include "commands_new/kis_switch_current_time_command.h" #include "kis_command_utils.h" #include "KisPart.h" #include "kis_animation_cache_populator.h" struct KisTimeBasedItemModel::Private { Private() : animationPlayer(0) , numFramesOverride(0) , activeFrameIndex(0) , scrubInProgress(false) , scrubStartFrame(-1) {} KisImageWSP image; KisAnimationFrameCacheWSP framesCache; QPointer animationPlayer; QVector cachedFrames; int numFramesOverride; int activeFrameIndex; bool scrubInProgress; int scrubStartFrame; QScopedPointer > scrubbingCompressor; int baseNumFrames() const { auto imageSP = image.toStrongRef(); if (!imageSP) return 0; KisImageAnimationInterface *i = imageSP->animationInterface(); if (!i) return 1; return i->totalLength(); } int effectiveNumFrames() const { if (image.isNull()) return 0; return qMax(baseNumFrames(), numFramesOverride); } int framesPerSecond() { return image->animationInterface()->framerate(); } }; KisTimeBasedItemModel::KisTimeBasedItemModel(QObject *parent) : QAbstractTableModel(parent) , m_d(new Private()) { KisConfig cfg(true); using namespace std::placeholders; std::function callback( std::bind(&KisTimeBasedItemModel::slotInternalScrubPreviewRequested, this, _1)); m_d->scrubbingCompressor.reset( new KisSignalCompressorWithParam(cfg.scrubbingUpdatesDelay(), callback, KisSignalCompressor::FIRST_ACTIVE)); } KisTimeBasedItemModel::~KisTimeBasedItemModel() {} void KisTimeBasedItemModel::setImage(KisImageWSP image) { KisImageWSP oldImage = m_d->image; m_d->image = image; if (image) { KisImageAnimationInterface *ai = image->animationInterface(); connect(ai, SIGNAL(sigFramerateChanged()), SLOT(slotFramerateChanged())); connect(ai, SIGNAL(sigUiTimeChanged(int)), SLOT(slotCurrentTimeChanged(int))); } if (image != oldImage) { beginResetModel(); endResetModel(); } } void KisTimeBasedItemModel::setFrameCache(KisAnimationFrameCacheSP cache) { if (KisAnimationFrameCacheSP(m_d->framesCache) == cache) return; if (m_d->framesCache) { m_d->framesCache->disconnect(this); } m_d->framesCache = cache; if (m_d->framesCache) { connect(m_d->framesCache, SIGNAL(changed()), SLOT(slotCacheChanged())); } } void KisTimeBasedItemModel::setAnimationPlayer(KisAnimationPlayer *player) { if (m_d->animationPlayer == player) return; if (m_d->animationPlayer) { m_d->animationPlayer->disconnect(this); } m_d->animationPlayer = player; if (m_d->animationPlayer) { connect(m_d->animationPlayer, SIGNAL(sigPlaybackStopped()), SLOT(slotPlaybackStopped())); connect(m_d->animationPlayer, SIGNAL(sigFrameChanged()), SLOT(slotPlaybackFrameChanged())); } } void KisTimeBasedItemModel::setLastVisibleFrame(int time) { const int growThreshold = m_d->effectiveNumFrames() - 3; const int growValue = time + 8; const int shrinkThreshold = m_d->effectiveNumFrames() - 12; const int shrinkValue = qMax(m_d->baseNumFrames(), qMin(growValue, shrinkThreshold)); const bool canShrink = m_d->effectiveNumFrames() > m_d->baseNumFrames(); if (time >= growThreshold) { beginInsertColumns(QModelIndex(), m_d->effectiveNumFrames(), growValue - 1); m_d->numFramesOverride = growValue; endInsertColumns(); } else if (time < shrinkThreshold && canShrink) { beginRemoveColumns(QModelIndex(), shrinkValue, m_d->effectiveNumFrames() - 1); m_d->numFramesOverride = shrinkValue; endRemoveColumns(); } } int KisTimeBasedItemModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_d->effectiveNumFrames(); } QVariant KisTimeBasedItemModel::data(const QModelIndex &index, int role) const { switch (role) { case ActiveFrameRole: { return index.column() == m_d->activeFrameIndex; } } return QVariant(); } bool KisTimeBasedItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; switch (role) { case ActiveFrameRole: { setHeaderData(index.column(), Qt::Horizontal, value, role); break; } } return false; } QVariant KisTimeBasedItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { switch (role) { case ActiveFrameRole: return section == m_d->activeFrameIndex; case FrameCachedRole: return m_d->cachedFrames.size() > section ? m_d->cachedFrames[section] : false; case FramesPerSecondRole: return m_d->framesPerSecond(); } } return QVariant(); } bool KisTimeBasedItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (orientation == Qt::Horizontal) { switch (role) { case ActiveFrameRole: if (value.toBool() && section != m_d->activeFrameIndex) { int prevFrame = m_d->activeFrameIndex; m_d->activeFrameIndex = section; scrubTo(m_d->activeFrameIndex, m_d->scrubInProgress); /** * Optimization Hack Alert: * * ideally, we should emit all four signals, but... The * point is this code is used in a tight loop during * playback, so it should run as fast as possible. To tell * the story short, commenting out these three lines makes * playback run 15% faster ;) */ if (m_d->scrubInProgress) { //emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); //emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); //emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); } else { emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); } } } } return false; } bool KisTimeBasedItemModel::removeFrames(const QModelIndexList &indexes) { KisAnimationUtils::FrameItemList frameItems; { KisImageBarrierLockerWithFeedback locker(m_d->image); Q_FOREACH (const QModelIndex &index, indexes) { int time = index.column(); Q_FOREACH(KisKeyframeChannel *channel, channelsAt(index)) { if (channel->keyframeAt(time)) { frameItems << KisAnimationUtils::FrameItem(channel->node(), channel->id(), index.column()); } } } } if (frameItems.isEmpty()) return false; KisAnimationUtils::removeKeyframes(m_d->image, frameItems); return true; } KUndo2Command* KisTimeBasedItemModel::createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, bool moveEmptyFrames, KUndo2Command *parentCommand) { if (srcIndexes.isEmpty()) return 0; if (offset.isNull()) return 0; KisAnimationUtils::sortPointsForSafeMove(&srcIndexes, offset); KisAnimationUtils::FrameItemList srcFrameItems; KisAnimationUtils::FrameItemList dstFrameItems; Q_FOREACH (const QModelIndex &srcIndex, srcIndexes) { QModelIndex dstIndex = index( srcIndex.row() + offset.y(), srcIndex.column() + offset.x()); KisNodeSP srcNode = nodeAt(srcIndex); KisNodeSP dstNode = nodeAt(dstIndex); if (!srcNode || !dstNode) return 0; Q_FOREACH(KisKeyframeChannel *channel, channelsAt(srcIndex)) { if (moveEmptyFrames || channel->keyframeAt(srcIndex.column())) { srcFrameItems << KisAnimationUtils::FrameItem(srcNode, channel->id(), srcIndex.column()); dstFrameItems << KisAnimationUtils::FrameItem(dstNode, channel->id(), dstIndex.column()); } } } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcFrameItems.size() == dstFrameItems.size(), 0); if (srcFrameItems.isEmpty()) return 0; return KisAnimationUtils::createMoveKeyframesCommand(srcFrameItems, dstFrameItems, copyFrames, moveEmptyFrames, parentCommand); } bool KisTimeBasedItemModel::removeFramesAndOffset(QModelIndexList indicesToRemove) { if (indicesToRemove.isEmpty()) return true; std::sort(indicesToRemove.begin(), indicesToRemove.end(), [] (const QModelIndex &lhs, const QModelIndex &rhs) { return lhs.column() > rhs.column(); }); const int minColumn = indicesToRemove.last().column(); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Remove frame and shift", "Remove %1 frames and shift", indicesToRemove.size())); { KisImageBarrierLockerWithFeedback locker(m_d->image); Q_FOREACH (const QModelIndex &index, indicesToRemove) { QModelIndexList indicesToOffset; for (int column = index.column() + 1; column < columnCount(); column++) { indicesToOffset << this->index(index.row(), column); } createOffsetFramesCommand(indicesToOffset, QPoint(-1, 0), false, true, parentCommand); } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = minColumn; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } bool KisTimeBasedItemModel::mirrorFrames(QModelIndexList indexes) { QScopedPointer parentCommand(new KUndo2Command(kundo2_i18n("Mirror Frames"))); { KisImageBarrierLockerWithFeedback locker(m_d->image); QMap rowsList; Q_FOREACH (const QModelIndex &index, indexes) { rowsList[index.row()].append(index); } Q_FOREACH (int row, rowsList.keys()) { QModelIndexList &list = rowsList[row]; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!list.isEmpty(), false); std::sort(list.begin(), list.end(), [] (const QModelIndex &lhs, const QModelIndex &rhs) { return lhs.column() < rhs.column(); }); auto srcIt = list.begin(); auto dstIt = list.end(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcIt != dstIt, false); --dstIt; QList channels = channelsAt(*srcIt).values(); while (srcIt < dstIt) { Q_FOREACH (KisKeyframeChannel *channel, channels) { channel->swapFrames(srcIt->column(), dstIt->column(), parentCommand.data()); } srcIt++; dstIt--; } } } KisProcessingApplicator::runSingleCommandStroke(m_d->image, new KisCommandUtils::SkipFirstRedoWrapper(parentCommand.take()), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } void KisTimeBasedItemModel::slotInternalScrubPreviewRequested(int time) { if (m_d->animationPlayer && !m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->displayFrame(time); } } void KisTimeBasedItemModel::setScrubState(bool active) { if (!m_d->scrubInProgress && active) { const int currentFrame = m_d->image->animationInterface()->currentUITime(); const bool hasCurrentFrameInCache = m_d->framesCache->frameStatus(currentFrame) == KisAnimationFrameCache::Cached; if(!hasCurrentFrameInCache) { KisPart::instance()->prioritizeFrameForCache(m_d->image, currentFrame); } m_d->scrubStartFrame = m_d->activeFrameIndex; m_d->scrubInProgress = true; } if (m_d->scrubInProgress && !active) { m_d->scrubInProgress = false; if (m_d->scrubStartFrame >= 0 && m_d->scrubStartFrame != m_d->activeFrameIndex) { scrubTo(m_d->activeFrameIndex, false); } m_d->scrubStartFrame = -1; } } void KisTimeBasedItemModel::scrubTo(int time, bool preview) { if (m_d->animationPlayer && m_d->animationPlayer->isPlaying()) return; KIS_ASSERT_RECOVER_RETURN(m_d->image); if (preview) { if (m_d->animationPlayer) { m_d->scrubbingCompressor->start(time); } } else { m_d->image->animationInterface()->requestTimeSwitchWithUndo(time); } } void KisTimeBasedItemModel::slotCurrentTimeChanged(int time) { if (time != m_d->activeFrameIndex) { setHeaderData(time, Qt::Horizontal, true, ActiveFrameRole); } } void KisTimeBasedItemModel::slotFramerateChanged() { emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); } void KisTimeBasedItemModel::slotCacheChanged() { const int numFrames = columnCount(); m_d->cachedFrames.resize(numFrames); for (int i = 0; i < numFrames; i++) { m_d->cachedFrames[i] = m_d->framesCache->frameStatus(i) == KisAnimationFrameCache::Cached; } emit headerDataChanged(Qt::Horizontal, 0, numFrames); } void KisTimeBasedItemModel::slotPlaybackFrameChanged() { if (!m_d->animationPlayer->isPlaying()) return; - setData(index(0, m_d->animationPlayer->currentTime()), true, ActiveFrameRole); + setData(index(0, m_d->animationPlayer->visibleFrame()), true, ActiveFrameRole); } void KisTimeBasedItemModel::slotPlaybackStopped() { setData(index(0, m_d->image->animationInterface()->currentUITime()), true, ActiveFrameRole); } void KisTimeBasedItemModel::setPlaybackRange(const KisTimeRange &range) { if (m_d->image.isNull()) return; KisImageAnimationInterface *i = m_d->image->animationInterface(); i->setPlaybackRange(range); } bool KisTimeBasedItemModel::isPlaybackActive() const { return m_d->animationPlayer && m_d->animationPlayer->isPlaying(); } +bool KisTimeBasedItemModel::isPlaybackPaused() const +{ + return m_d->animationPlayer && m_d->animationPlayer->isPaused(); +} + +void KisTimeBasedItemModel::stopPlayback() const { + KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->animationPlayer); + m_d->animationPlayer->stop(); +} + int KisTimeBasedItemModel::currentTime() const { return m_d->image->animationInterface()->currentUITime(); } KisImageWSP KisTimeBasedItemModel::image() const { return m_d->image; } diff --git a/plugins/dockers/animation/kis_time_based_item_model.h b/plugins/dockers/animation/kis_time_based_item_model.h index 26c8cf01ce..5e3dc7b33f 100644 --- a/plugins/dockers/animation/kis_time_based_item_model.h +++ b/plugins/dockers/animation/kis_time_based_item_model.h @@ -1,106 +1,107 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_TIME_BASED_ITEM_MODEL_H #define _KIS_TIME_BASED_ITEM_MODEL_H #include #include #include #include "kritaanimationdocker_export.h" #include "kis_types.h" class KisTimeRange; class KisAnimationPlayer; class KisKeyframeChannel; class KRITAANIMATIONDOCKER_EXPORT KisTimeBasedItemModel : public QAbstractTableModel { Q_OBJECT public: KisTimeBasedItemModel(QObject *parent); ~KisTimeBasedItemModel() override; void setImage(KisImageWSP image); void setFrameCache(KisAnimationFrameCacheSP cache); void setAnimationPlayer(KisAnimationPlayer *player); void setLastVisibleFrame(int time); int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override; bool removeFrames(const QModelIndexList &indexes); bool removeFramesAndOffset(QModelIndexList indicesToRemove); bool mirrorFrames(QModelIndexList indexes); void setScrubState(bool active); void scrubTo(int time, bool preview); void setPlaybackRange(const KisTimeRange &range); bool isPlaybackActive() const; + bool isPlaybackPaused() const; + void stopPlayback() const; int currentTime() const; enum ItemDataRole { ActiveFrameRole = Qt::UserRole + 101, FrameExistsRole, SpecialKeyframeExists, FrameCachedRole, FrameEditableRole, FramesPerSecondRole, UserRole, FrameHasContent // is it an empty frame with nothing in it? }; protected: virtual KisNodeSP nodeAt(QModelIndex index) const = 0; virtual QMap channelsAt(QModelIndex index) const = 0; KisImageWSP image() const; KUndo2Command* createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, bool moveEmptyFrames, KUndo2Command *parentCommand = 0); protected Q_SLOTS: void slotCurrentTimeChanged(int time); private Q_SLOTS: void slotFramerateChanged(); void slotCacheChanged(); void slotInternalScrubPreviewRequested(int time); void slotPlaybackFrameChanged(); void slotPlaybackStopped(); - private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/animation/timeline_docker.cpp b/plugins/dockers/animation/timeline_docker.cpp index 77198e8b33..bb03f2a6b4 100644 --- a/plugins/dockers/animation/timeline_docker.cpp +++ b/plugins/dockers/animation/timeline_docker.cpp @@ -1,165 +1,603 @@ /* * Copyright (c) 2015 Jouni Pentikäinen + * Copyright (c) 2020 Emmet O'Neill + * Copyright (c) 2020 Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_docker.h" +#include +#include "QHBoxLayout" +#include "QVBoxLayout" +#include "QFormLayout" +#include "QLabel" +#include "QPushButton" +#include "QToolButton" +#include "QMenu" +#include "QWidgetAction" + +#include "krita_utils.h" #include "kis_canvas2.h" #include "kis_image.h" #include #include "KisViewManager.h" #include "kis_paint_layer.h" #include "KisDocument.h" #include "kis_dummies_facade.h" #include "kis_shape_controller.h" #include "kis_action.h" #include "kis_action_manager.h" +#include "kis_animation_player.h" +#include "kis_animation_utils.h" +#include "kis_image_config.h" +#include "kis_keyframe_channel.h" +#include "kis_image.h" #include "timeline_frames_model.h" #include "timeline_frames_view.h" +#include "kis_time_range.h" #include "kis_animation_frame_cache.h" #include "kis_image_animation_interface.h" #include "kis_signal_auto_connection.h" #include "kis_node_manager.h" +#include "kis_transport_controls.h" +#include "kis_int_parse_spin_box.h" +#include "kis_slider_spin_box.h" +#include "kis_signals_blocker.h" -#include +TimelineDockerTitleBar::TimelineDockerTitleBar(QWidget* parent) : + KisUtilityTitleBar(new QLabel(i18n("Animation Timeline"), parent), parent) +{ + // Transport Controls... + transport = new KisTransportControls(this); + widgetArea->addWidget(transport); + + widgetArea->addSpacing(SPACING_UNIT); + + // Frame Register... + frameRegister = new KisIntParseSpinBox(this); + frameRegister->setToolTip(i18n("Frame register")); + frameRegister->setPrefix("# "); + frameRegister->setRange(0, MAX_FRAMES); + widgetArea->addWidget(frameRegister); + + widgetArea->addSpacing(SPACING_UNIT); + + { // Frame ops... + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setSpacing(0); + layout->setContentsMargins(0,0,0,0); + + btnAddKeyframe = new QToolButton(this); + layout->addWidget(btnAddKeyframe); + + btnDuplicateKeyframe = new QToolButton(this); + layout->addWidget(btnDuplicateKeyframe); + + btnRemoveKeyframe = new QToolButton(this); + layout->addWidget(btnRemoveKeyframe); + + QWidget *widget = new QWidget(); + widget->setLayout(layout); + + widgetArea->addWidget(widget); + } + + widgetArea->addSpacing(SPACING_UNIT); + + // Drop Frames.. + btnDropFrames = new QToolButton(this); + widgetArea->addWidget(btnDropFrames); + + // Playback Speed.. + sbSpeed = new KisSliderSpinBox(this); + sbSpeed->setRange(25, 200); + sbSpeed->setSingleStep(5); + sbSpeed->setValue(100); + sbSpeed->setPrefix("Speed: "); + sbSpeed->setSuffix(" %"); + sbSpeed->setToolTip(i18n("Preview playback speed")); + widgetArea->addWidget(sbSpeed); + + widgetArea->addStretch(); + + { // Menus.. + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setSpacing(0); + layout->setContentsMargins(SPACING_UNIT,0,0,0); + + // Onion skins menu. + btnOnionSkinsMenu = new QPushButton(KisIconUtils::loadIcon("onion_skin_options"), "", this); + btnOnionSkinsMenu->setToolTip(i18n("Onion skins menu")); + layout->addWidget(btnOnionSkinsMenu); + + // Audio menu.. + btnAudioMenu = new QPushButton(KisIconUtils::loadIcon("audio-none"), "", this); + btnOnionSkinsMenu->setToolTip(i18n("Audio menu")); + btnAudioMenu->hide(); // (NOTE: Hidden for now while audio features develop.) + layout->addWidget(btnAudioMenu); + + { // Settings menu.. + btnSettingsMenu = new QToolButton(this); + btnSettingsMenu->setIcon(KisIconUtils::loadIcon("configure")); + btnSettingsMenu->setToolTip(i18n("Animation settings menu")); + + QWidget *settingsMenuWidget = new QWidget(this); + settingsMenuWidget->setLayout(new QHBoxLayout(settingsMenuWidget)); + + QWidget *fields = new QWidget(settingsMenuWidget); + QFormLayout *fieldsLayout = new QFormLayout(settingsMenuWidget); + fields->setLayout(fieldsLayout); + + sbStartFrame = new KisIntParseSpinBox(settingsMenuWidget); + sbStartFrame->setMaximum(10000); + fieldsLayout->addRow(i18n("Clip Start: "), sbStartFrame); + + sbEndFrame = new KisIntParseSpinBox(settingsMenuWidget); + sbEndFrame->setMaximum(10000); + fieldsLayout->addRow(i18n("Clip End: "), sbEndFrame); + + sbFrameRate = new KisIntParseSpinBox(settingsMenuWidget); + sbFrameRate->setMinimum(0); + sbFrameRate->setMaximum(180); + fieldsLayout->addRow(i18n("Frame Rate: "), sbFrameRate); + + QWidget *buttons = new QWidget(settingsMenuWidget); + buttons->setLayout(new QVBoxLayout(settingsMenuWidget)); + + btnAutoFrame = new QToolButton(settingsMenuWidget); + buttons->layout()->addWidget(btnAutoFrame); + buttons->layout()->setAlignment(Qt::AlignTop); + + settingsMenuWidget->layout()->addWidget(fields); + settingsMenuWidget->layout()->addWidget(buttons); + + layout->addWidget(btnSettingsMenu); + + QMenu *settingsPopMenu = new QMenu(this); + QWidgetAction *settingsMenuAction = new QWidgetAction(this); + settingsMenuAction->setDefaultWidget(settingsMenuWidget); + settingsPopMenu->addAction(settingsMenuAction); + + btnSettingsMenu->setPopupMode(QToolButton::InstantPopup); + btnSettingsMenu->setMenu(settingsPopMenu); + } + + QWidget *widget = new QWidget(); + widget->setLayout(layout); + widgetArea->addWidget(widget); + } +} struct TimelineDocker::Private { Private(QWidget *parent) - : model(new TimelineFramesModel(parent)), - view(new TimelineFramesView(parent)) + : framesModel(new TimelineFramesModel(parent)), + framesView(new TimelineFramesView(parent)), + titlebar(new TimelineDockerTitleBar(parent)), + mainWindow(nullptr) { - view->setModel(model); + framesView->setModel(framesModel); } - TimelineFramesModel *model; - TimelineFramesView *view; + TimelineFramesModel *framesModel; + TimelineFramesView *framesView; + TimelineDockerTitleBar *titlebar; QPointer canvas; KisSignalAutoConnectionsStore canvasConnections; + KisMainWindow *mainWindow; }; TimelineDocker::TimelineDocker() - : QDockWidget(i18n("Timeline")) + : QDockWidget(i18n("Animation Timeline")) , m_d(new Private(this)) { - setWidget(m_d->view); + setWidget(m_d->framesView); + setTitleBarWidget(m_d->titlebar); + + connect(m_d->titlebar->transport, SIGNAL(back()), this, SLOT(previousFrame())); + connect(m_d->titlebar->transport, SIGNAL(stop()), this, SLOT(stop())); + connect(m_d->titlebar->transport, SIGNAL(playPause()), this, SLOT(playPause())); + connect(m_d->titlebar->transport, SIGNAL(forward()), this, SLOT(nextFrame())); + + connect(m_d->titlebar->frameRegister, SIGNAL(valueChanged(int)), SLOT(goToFrame(int))); + connect(m_d->titlebar->sbStartFrame, SIGNAL(valueChanged(int)), SLOT(setStartFrame(int))); + connect(m_d->titlebar->sbEndFrame, SIGNAL(valueChanged(int)), SLOT(setEndFrame(int))); + connect(m_d->titlebar->sbFrameRate, SIGNAL(valueChanged(int)), SLOT(setFrameRate(int))); + connect(m_d->titlebar->sbSpeed, SIGNAL(valueChanged(int)), SLOT(setPlaybackSpeed(int))); + + connect(m_d->titlebar->btnOnionSkinsMenu, &QPushButton::released, [this](){ + if (m_d->mainWindow) { + QDockWidget *docker = m_d->mainWindow->dockWidget("OnionSkinsDocker"); + if (docker) { + docker->setVisible(!docker->isVisible()); + } + } + }); } TimelineDocker::~TimelineDocker() { } struct NodeManagerInterface : TimelineFramesModel::NodeManipulationInterface { NodeManagerInterface(KisNodeManager *manager) : m_manager(manager) {} KisLayerSP addPaintLayer() const override { return m_manager->createPaintLayer(); } void removeNode(KisNodeSP node) const override { m_manager->removeSingleNode(node); } bool setNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const override { return m_manager->trySetNodeProperties(node, image, properties); } private: KisNodeManager *m_manager; }; void TimelineDocker::setCanvas(KoCanvasBase * canvas) { - if (canvas && m_d->canvas == canvas) return; + if (m_d->canvas == canvas) return; - if (m_d->model->hasConnectionToCanvas()) { + if (m_d->framesModel->hasConnectionToCanvas()) { m_d->canvasConnections.clear(); - m_d->model->setDummiesFacade(0, 0, 0); - m_d->model->setFrameCache(0); - m_d->model->setAnimationPlayer(0); - m_d->model->setNodeManipulationInterface(0); + m_d->framesModel->setDummiesFacade(0, 0, 0); + m_d->framesModel->setFrameCache(0); + m_d->framesModel->setAnimationPlayer(0); + m_d->framesModel->setNodeManipulationInterface(0); + } - if (m_d->canvas) { - m_d->canvas->disconnectCanvasObserver(this); + if (m_d->canvas) { + m_d->canvas->disconnectCanvasObserver(this); + m_d->canvas->animationPlayer()->disconnect(this); + + if(m_d->canvas->image()) { + m_d->canvas->image()->animationInterface()->disconnect(this); } } m_d->canvas = dynamic_cast(canvas); setEnabled(m_d->canvas != 0); if(m_d->canvas) { KisDocument *doc = static_cast(m_d->canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); - m_d->model->setDummiesFacade(kritaShapeController, + m_d->framesModel->setDummiesFacade(kritaShapeController, m_d->canvas->image(), m_d->canvas->viewManager()->nodeManager()->nodeDisplayModeAdapter()); - slotUpdateFrameCache(); - m_d->model->setAnimationPlayer(m_d->canvas->animationPlayer()); + updateFrameCache(); + + { + KisSignalsBlocker blocker(m_d->titlebar->sbStartFrame, + m_d->titlebar->sbEndFrame, + m_d->titlebar->sbFrameRate); + + KisImageAnimationInterface *animinterface = m_d->canvas->image()->animationInterface(); + m_d->titlebar->sbStartFrame->setValue(animinterface->fullClipRange().start()); + m_d->titlebar->sbEndFrame->setValue(animinterface->fullClipRange().end()); + m_d->titlebar->sbFrameRate->setValue(animinterface->framerate()); + m_d->titlebar->sbSpeed->setValue(100); + m_d->titlebar->frameRegister->setValue(animinterface->currentTime()); + } + + + m_d->framesModel->setAnimationPlayer(m_d->canvas->animationPlayer()); - m_d->model->setNodeManipulationInterface( + m_d->framesModel->setNodeManipulationInterface( new NodeManagerInterface(m_d->canvas->viewManager()->nodeManager())); m_d->canvasConnections.addConnection( m_d->canvas->viewManager()->nodeManager(), SIGNAL(sigNodeActivated(KisNodeSP)), - m_d->model, SLOT(slotCurrentNodeChanged(KisNodeSP))); + m_d->framesModel, SLOT(slotCurrentNodeChanged(KisNodeSP))); m_d->canvasConnections.addConnection( - m_d->model, SIGNAL(requestCurrentNodeChanged(KisNodeSP)), + m_d->framesModel, SIGNAL(requestCurrentNodeChanged(KisNodeSP)), m_d->canvas->viewManager()->nodeManager(), SLOT(slotNonUiActivatedNode(KisNodeSP))); - m_d->model->slotCurrentNodeChanged(m_d->canvas->viewManager()->activeNode()); + m_d->framesModel->slotCurrentNodeChanged(m_d->canvas->viewManager()->activeNode()); m_d->canvasConnections.addConnection( m_d->canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), - this, SLOT(slotUpdateIcons())); + this, SLOT(handleThemeChange())); m_d->canvasConnections.addConnection( m_d->canvas, SIGNAL(sigCanvasEngineChanged()), - this, SLOT(slotUpdateFrameCache())); + this, SLOT(updateFrameCache())); + + + connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigUiTimeChanged(int)), this, SLOT(updateFrameRegister())); + connect(m_d->canvas->animationPlayer(), SIGNAL(sigFrameChanged()), this, SLOT(updateFrameRegister())); + connect(m_d->canvas->animationPlayer(), SIGNAL(sigPlaybackStopped()), this, SLOT(updateFrameRegister())); + + connect(m_d->canvas->animationPlayer(), SIGNAL(sigPlaybackStateChanged(bool)), m_d->titlebar->frameRegister, SLOT(setDisabled(bool))); + connect(m_d->canvas->animationPlayer(), SIGNAL(sigPlaybackStateChanged(bool)), m_d->titlebar->transport, SLOT(setPlaying(bool))); + connect(m_d->canvas->animationPlayer(), SIGNAL(sigPlaybackStatisticsUpdated()), this, SLOT(updatePlaybackStatistics())); + + connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SLOT(handleClipRangeChange())); + connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), SLOT(handleFrameRateChange())); } +} + +void TimelineDocker::handleThemeChange() +{ + if (m_d->framesView) { + m_d->framesView->slotUpdateIcons(); + } +} +void TimelineDocker::updateFrameCache() +{ + m_d->framesModel->setFrameCache(m_d->canvas->frameCache()); } -void TimelineDocker::slotUpdateIcons() +void TimelineDocker::updateFrameRegister() { - if (m_d->view) { - m_d->view->slotUpdateIcons(); + if (!m_d->canvas && !m_d->canvas->image()) { + return; } + + const int frame = m_d->canvas->animationPlayer()->isPlaying() ? + m_d->canvas->animationPlayer()->visibleFrame() : + m_d->canvas->image()->animationInterface()->currentUITime(); + + m_d->titlebar->frameRegister->setValue(frame); } -void TimelineDocker::slotUpdateFrameCache() +void TimelineDocker::updatePlaybackStatistics() { - m_d->model->setFrameCache(m_d->canvas->frameCache()); + qreal effectiveFps = 0.0; + qreal realFps = 0.0; + qreal framesDropped = 0.0; + bool isPlaying = false; + + KisAnimationPlayer *player = m_d->canvas && m_d->canvas->animationPlayer() ? m_d->canvas->animationPlayer() : 0; + if (player) { + effectiveFps = player->effectiveFps(); + realFps = player->realFps(); + framesDropped = player->framesDroppedPortion(); + isPlaying = player->isPlaying(); + } + + KisConfig cfg(true); + const bool shouldDropFrames = cfg.animationDropFrames(); + + QAction *action = m_d->titlebar->btnDropFrames->defaultAction(); + const bool droppingFrames = shouldDropFrames && framesDropped > 0.05; + action->setIcon(KisIconUtils::loadIcon(droppingFrames ? "droppedframes" : "dropframe")); + + QString actionText; + if (!isPlaying) { + actionText = QString("%1 (%2) \n%3") + .arg(KisAnimationUtils::dropFramesActionName) + .arg(KritaUtils::toLocalizedOnOff(shouldDropFrames)) + .arg(i18n("Enable to preserve playback timing.")); + } else { + actionText = QString("%1 (%2)\n" + "%3\n" + "%4\n" + "%5") + .arg(KisAnimationUtils::dropFramesActionName) + .arg(KritaUtils::toLocalizedOnOff(shouldDropFrames)) + .arg(i18n("Effective FPS:\t%1", effectiveFps)) + .arg(i18n("Real FPS:\t%1", realFps)) + .arg(i18n("Frames dropped:\t%1\%", framesDropped * 100)); + } + action->setText(actionText); } void TimelineDocker::unsetCanvas() { setCanvas(0); } void TimelineDocker::setViewManager(KisViewManager *view) { + m_d->mainWindow = view->mainWindow(); + KisActionManager *actionManager = view->actionManager(); - m_d->view->setActionManager(actionManager); - m_d->view->setPinToTimeline(actionManager->actionByName("pin_to_timeline")); + m_d->framesView->setActionManager(actionManager); + + KisAction *action = 0; + + TimelineDockerTitleBar* titleBar = static_cast(titleBarWidget()); + + action = actionManager->actionByName("add_blank_frame"); + titleBar->btnAddKeyframe->setDefaultAction(action); + + action = actionManager->actionByName("add_duplicate_frame"); + titleBar->btnDuplicateKeyframe->setDefaultAction(action); + + action = actionManager->actionByName("remove_frames"); + titleBar->btnRemoveKeyframe->setDefaultAction(action); + + action = actionManager->createAction("toggle_playback"); + action->setActivationFlags(KisAction::ACTIVE_IMAGE); + connect(action, SIGNAL(triggered(bool)), SLOT(playPause())); + + action = actionManager->createAction("stop_playback"); + action->setActivationFlags(KisAction::ACTIVE_IMAGE); + connect(action, SIGNAL(triggered(bool)), SLOT(stop())); + + action = actionManager->createAction("previous_frame"); + action->setActivationFlags(KisAction::ACTIVE_IMAGE); + connect(action, SIGNAL(triggered(bool)), SLOT(previousFrame())); + + action = actionManager->createAction("next_frame"); + action->setActivationFlags(KisAction::ACTIVE_IMAGE); + connect(action, SIGNAL(triggered(bool)), SLOT(nextFrame())); + + action = actionManager->createAction("auto_key"); + m_d->titlebar->btnAutoFrame->setDefaultAction(action); + connect(action, SIGNAL(triggered(bool)), SLOT(setAutoKey(bool))); + + { + KisImageConfig config(true); + action->setChecked(config.autoKeyEnabled()); + action->setIcon(config.autoKeyEnabled() ? KisIconUtils::loadIcon("auto-key-on") : KisIconUtils::loadIcon("auto-key-off")); + } + + action = actionManager->createAction("drop_frames"); + m_d->titlebar->btnDropFrames->setDefaultAction(action); + connect(action, SIGNAL(triggered(bool)), SLOT(setDropFrames(bool))); + + { + KisConfig config(true); + action->setChecked(config.animationDropFrames()); + } +} + +void TimelineDocker::playPause() +{ + if (!m_d->canvas) return; + + if (m_d->canvas->animationPlayer()->isPlaying()) { + m_d->canvas->animationPlayer()->pause(); + } else { + m_d->canvas->animationPlayer()->play(); + } +} + +void TimelineDocker::stop() +{ + if (!m_d->canvas) return; + + if( m_d->canvas->animationPlayer()->isStopped()) { + m_d->canvas->animationPlayer()->goToStartFrame(); + } else { + m_d->canvas->animationPlayer()->stop(); + m_d->canvas->animationPlayer()->goToPlaybackOrigin(); + } +} + +void TimelineDocker::previousFrame() +{ + if (!m_d->canvas) return; + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + + int time = animInterface->currentUITime() - 1; + if (time >= 0) { + animInterface->requestTimeSwitchWithUndo(time); + } +} + +void TimelineDocker::nextFrame() +{ + if (!m_d->canvas) return; + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + + int time = animInterface->currentUITime() + 1; + animInterface->requestTimeSwitchWithUndo(time); +} + +void TimelineDocker::goToFrame(int frameIndex) +{ + if (!m_d->canvas || !m_d->canvas->image()) return; + + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + + if (m_d->canvas->animationPlayer()->isPlaying() || + frameIndex == animInterface->currentUITime()) { + return; + } + + animInterface->requestTimeSwitchWithUndo(frameIndex); } + +void TimelineDocker::setStartFrame(int frame) +{ + if (!m_d->canvas || !m_d->canvas->image()) return; + + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + + animInterface->setFullClipRangeStartTime(frame); +} + +void TimelineDocker::setEndFrame(int frame) +{ + if (!m_d->canvas || !m_d->canvas->image()) return; + + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + + animInterface->setFullClipRangeEndTime(frame); +} + +void TimelineDocker::setFrameRate(int framerate) +{ + if (!m_d->canvas || !m_d->canvas->image()) return; + + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + + animInterface->setFramerate(framerate); +} + +void TimelineDocker::setPlaybackSpeed(int playbackSpeed) +{ + if (!m_d->canvas || !m_d->canvas->image()) return; + + const float normalizedSpeed = playbackSpeed / 100.0; + m_d->canvas->animationPlayer()->slotUpdatePlaybackSpeed(normalizedSpeed); +} + +void TimelineDocker::setDropFrames(bool dropFrames) +{ + KisConfig cfg(false); + if (dropFrames != cfg.animationDropFrames()) { + cfg.setAnimationDropFrames(dropFrames); + updatePlaybackStatistics(); + } +} + +void TimelineDocker::setAutoKey(bool autoKey) +{ + KisImageConfig cfg(false); + if (autoKey != cfg.autoKeyEnabled()) { + cfg.setAutoKeyEnabled(autoKey); + const QIcon icon = cfg.autoKeyEnabled() ? KisIconUtils::loadIcon("auto-key-on") : KisIconUtils::loadIcon("auto-key-off"); + QAction* action = m_d->titlebar->btnAutoFrame->defaultAction(); + action->setIcon(icon); + } +} + +void TimelineDocker::handleClipRangeChange() +{ + if (!m_d->canvas || !m_d->canvas->image()) return; + + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + + m_d->titlebar->sbStartFrame->setValue(animInterface->fullClipRange().start()); + m_d->titlebar->sbEndFrame->setValue(animInterface->fullClipRange().end()); +} + +void TimelineDocker::handleFrameRateChange() +{ + if (!m_d->canvas || !m_d->canvas->image()) return; + + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + + m_d->titlebar->sbFrameRate->setValue( m_d->canvas->image()->animationInterface()->framerate() ); +} + + diff --git a/plugins/dockers/animation/timeline_docker.h b/plugins/dockers/animation/timeline_docker.h index d9a2261d7c..b59f548ab4 100644 --- a/plugins/dockers/animation/timeline_docker.h +++ b/plugins/dockers/animation/timeline_docker.h @@ -1,54 +1,135 @@ /* * Copyright (c) 2015 Jouni Pentikäinen + * Copyright (c) 2020 Emmet O'Neill + * Copyright (c) 2020 Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _TIMELINE_DOCKER_H_ #define _TIMELINE_DOCKER_H_ #include "kritaimage_export.h" +#include #include + #include +#include -#include +#ifdef Q_OS_MACOS +#include +#endif + +class QPushButton; +class QToolButton; +class KisTransportControls; +class KisIntParseSpinBox; +class KisSliderSpinBox; class KisCanvas2; class KisAction; + +/* + * A customized titlebar for the Animation Timeline Docker that's + * packed with useful widgets and menus. + * + * To avoid cluttering the UI, elements that are important to the + * animator's workflow should be available at a glace, while + * set-and-forget types of things should be hidden inside of menus. + */ +class TimelineDockerTitleBar : public KisUtilityTitleBar +{ + Q_OBJECT + +public: + TimelineDockerTitleBar(QWidget *parent = nullptr); + + KisTransportControls* transport; + + KisIntParseSpinBox *frameRegister; + + QToolButton *btnAddKeyframe; + QToolButton *btnDuplicateKeyframe; + QToolButton *btnRemoveKeyframe; + + QPushButton *btnOnionSkinsMenu; + QPushButton *btnAudioMenu; + QToolButton *btnSettingsMenu; + + KisIntParseSpinBox *sbStartFrame; + KisIntParseSpinBox *sbEndFrame; + KisIntParseSpinBox *sbFrameRate; + KisSliderSpinBox *sbSpeed; + QToolButton *btnAutoFrame; + QToolButton *btnDropFrames; + +private: + const int MAX_FRAMES = 9999; +}; + + +/* + * Krita's Animation Timeline Docker. + * This is the GUI heart of Krita's traditional animation workflow, + * and is where artists can configure, edit, scrub and play their animation. + * + * Currently interacts with the TimelinFramesView/Model as well as + * the KisImageAnimationInterface. (TODO: Consider refactoring to + * streamline this interaction towards Docker -> AnimationPlayer -> ImageAnimInterface) + */ class TimelineDocker : public QDockWidget, public KisMainwindowObserver { Q_OBJECT public: TimelineDocker(); ~TimelineDocker() override; - QString observerName() override { return "TimelineDocker"; } + QString observerName() override { return "AnimationTimelineDocker"; } void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; void setViewManager(KisViewManager *kisview) override; public Q_SLOTS: - void slotUpdateIcons(); - void slotUpdateFrameCache(); + void playPause(); + void stop(); + void previousFrame(); + void nextFrame(); + + void goToFrame(int frameIndex); + void setStartFrame(int frame); + void setEndFrame(int frame); + void setFrameRate(int frmaerate); + void setPlaybackSpeed(int playbackSpeed); + void setDropFrames(bool dropFrames); + void setAutoKey(bool autoKey); + + void handleClipRangeChange(); + void handleFrameRateChange(); + + void updateFrameCache(); + void updateFrameRegister(); + void updatePlaybackStatistics(); + + void handleThemeChange(); private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp index 810f57ad44..b692d64c89 100644 --- a/plugins/dockers/animation/timeline_frames_view.cpp +++ b/plugins/dockers/animation/timeline_frames_view.cpp @@ -1,1572 +1,1552 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_view.h" #include "timeline_frames_model.h" #include "timeline_ruler_header.h" #include "timeline_layers_header.h" #include "timeline_insert_keyframe_dialog.h" #include "timeline_frames_item_delegate.h" #include #include #include #include #include #include #include #include #include #include #include "config-qtmultimedia.h" #include "KSharedConfig" #include "KisKineticScroller.h" #include "kis_zoom_button.h" #include "kis_icon_utils.h" #include "kis_animation_utils.h" #include "kis_custom_modifiers_catcher.h" #include "kis_action.h" #include "kis_signal_compressor.h" #include "kis_time_range.h" #include "kis_color_label_selector_widget.h" #include "kis_keyframe_channel.h" #include "kis_slider_spin_box.h" #include #include #include #include #include typedef QPair QItemViewPaintPair; typedef QList QItemViewPaintPairs; struct TimelineFramesView::Private { Private(TimelineFramesView *_q) : q(_q), fps(1), zoomStillPointIndex(-1), zoomStillPointOriginalOffset(0), dragInProgress(false), dragWasSuccessful(false), modifiersCatcher(0), selectionChangedCompressor(300, KisSignalCompressor::FIRST_INACTIVE) {} TimelineFramesView *q; TimelineFramesModel *model; TimelineRulerHeader *horizontalRuler; TimelineLayersHeader *layersHeader; int fps; int zoomStillPointIndex; int zoomStillPointOriginalOffset; QPoint initialDragPanValue; QPoint initialDragPanPos; QToolButton *addLayersButton; KisAction *pinLayerToTimelineAction; QToolButton *audioOptionsButton; KisColorLabelSelectorWidget *colorSelector; QWidgetAction *colorSelectorAction; KisColorLabelSelectorWidget *multiframeColorSelector; QWidgetAction *multiframeColorSelectorAction; QMenu *audioOptionsMenu; QAction *openAudioAction; QAction *audioMuteAction; KisSliderSpinBox *volumeSlider; QMenu *layerEditingMenu; QMenu *existingLayersMenu; TimelineInsertKeyframeDialog *insertKeyframeDialog; KisZoomButton *zoomDragButton; bool dragInProgress; bool dragWasSuccessful; KisCustomModifiersCatcher *modifiersCatcher; QPoint lastPressedPosition; Qt::KeyboardModifiers lastPressedModifier; KisSignalCompressor selectionChangedCompressor; QStyleOptionViewItem viewOptionsV4() const; QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const; QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const; KisIconToolTip tip; KisActionManager *actionMan = 0; }; TimelineFramesView::TimelineFramesView(QWidget *parent) : QTableView(parent), m_d(new Private(this)) { m_d->modifiersCatcher = new KisCustomModifiersCatcher(this); m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space); m_d->modifiersCatcher->addModifier("offset-frame", Qt::Key_Alt); setCornerButtonEnabled(false); setSelectionBehavior(QAbstractItemView::SelectItems); setSelectionMode(QAbstractItemView::ExtendedSelection); setItemDelegate(new TimelineFramesItemDelegate(this)); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDropIndicatorShown(true); setDefaultDropAction(Qt::MoveAction); m_d->horizontalRuler = new TimelineRulerHeader(this); this->setHorizontalHeader(m_d->horizontalRuler); connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnLeft()), SLOT(slotInsertKeyframeColumnLeft())); connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnRight()), SLOT(slotInsertKeyframeColumnRight())); connect(m_d->horizontalRuler, SIGNAL(sigInsertMultipleColumns()), SLOT(slotInsertMultipleKeyframeColumns())); connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumns()), SLOT(slotRemoveSelectedColumns())); connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumnsAndShift()), SLOT(slotRemoveSelectedColumnsAndShift())); connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumns()), SLOT(slotInsertHoldFrameColumn())); connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumns()), SLOT(slotRemoveHoldFrameColumn())); connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumnsCustom()), SLOT(slotInsertMultipleHoldFrameColumns())); connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumnsCustom()), SLOT(slotRemoveMultipleHoldFrameColumns())); connect(m_d->horizontalRuler, SIGNAL(sigMirrorColumns()), SLOT(slotMirrorColumns())); connect(m_d->horizontalRuler, SIGNAL(sigCopyColumns()), SLOT(slotCopyColumns())); connect(m_d->horizontalRuler, SIGNAL(sigCutColumns()), SLOT(slotCutColumns())); connect(m_d->horizontalRuler, SIGNAL(sigPasteColumns()), SLOT(slotPasteColumns())); m_d->layersHeader = new TimelineLayersHeader(this); m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed); m_d->layersHeader->setDefaultSectionSize(24); m_d->layersHeader->setMinimumWidth(60); m_d->layersHeader->setHighlightSections(true); this->setVerticalHeader(m_d->layersHeader); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(slotUpdateInfiniteFramesCount())); connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount())); /********** Layer Menu ***********************************************************/ m_d->layerEditingMenu = new QMenu(this); m_d->layerEditingMenu->addSection(i18n("Edit Layers:")); m_d->layerEditingMenu->addSeparator(); m_d->layerEditingMenu->addAction(KisAnimationUtils::newLayerActionName, this, SLOT(slotAddNewLayer())); m_d->layerEditingMenu->addAction(KisAnimationUtils::removeLayerActionName, this, SLOT(slotRemoveLayer())); m_d->layerEditingMenu->addSeparator(); m_d->existingLayersMenu = m_d->layerEditingMenu->addMenu(KisAnimationUtils::pinExistingLayerActionName); connect(m_d->existingLayersMenu, SIGNAL(aboutToShow()), SLOT(slotUpdateLayersMenu())); connect(m_d->existingLayersMenu, SIGNAL(triggered(QAction*)), SLOT(slotAddExistingLayer(QAction*))); connect(m_d->layersHeader, SIGNAL(sigRequestContextMenu(QPoint)), SLOT(slotLayerContextMenuRequested(QPoint))); m_d->addLayersButton = new QToolButton(this); m_d->addLayersButton->setAutoRaise(true); m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->addLayersButton->setIconSize(QSize(20, 20)); m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup); m_d->addLayersButton->setMenu(m_d->layerEditingMenu); /********** Audio Channel Menu *******************************************************/ m_d->audioOptionsButton = new QToolButton(this); m_d->audioOptionsButton->setAutoRaise(true); m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none")); m_d->audioOptionsButton->setIconSize(QSize(20, 20)); // very small on windows if not explicitly set m_d->audioOptionsButton->setPopupMode(QToolButton::InstantPopup); m_d->audioOptionsMenu = new QMenu(this); m_d->audioOptionsMenu->addSection(i18n("Edit Audio:")); m_d->audioOptionsMenu->addSeparator(); #ifndef HAVE_QT_MULTIMEDIA m_d->audioOptionsMenu->addSection(i18nc("@item:inmenu", "Audio playback is not supported in this build!")); #endif m_d->openAudioAction= new QAction("XXX", this); connect(m_d->openAudioAction, SIGNAL(triggered()), this, SLOT(slotSelectAudioChannelFile())); m_d->audioOptionsMenu->addAction(m_d->openAudioAction); m_d->audioMuteAction = new QAction(i18nc("@item:inmenu", "Mute"), this); m_d->audioMuteAction->setCheckable(true); connect(m_d->audioMuteAction, SIGNAL(triggered(bool)), SLOT(slotAudioChannelMute(bool))); m_d->audioOptionsMenu->addAction(m_d->audioMuteAction); m_d->audioOptionsMenu->addAction(i18nc("@item:inmenu", "Remove audio"), this, SLOT(slotAudioChannelRemove())); m_d->audioOptionsMenu->addSeparator(); m_d->volumeSlider = new KisSliderSpinBox(this); m_d->volumeSlider->setRange(0, 100); m_d->volumeSlider->setSuffix(i18n("%")); m_d->volumeSlider->setPrefix(i18nc("@item:inmenu, slider", "Volume:")); m_d->volumeSlider->setSingleStep(1); m_d->volumeSlider->setPageStep(10); m_d->volumeSlider->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); connect(m_d->volumeSlider, SIGNAL(valueChanged(int)), SLOT(slotAudioVolumeChanged(int))); QWidgetAction *volumeAction = new QWidgetAction(m_d->audioOptionsMenu); volumeAction->setDefaultWidget(m_d->volumeSlider); m_d->audioOptionsMenu->addAction(volumeAction); m_d->audioOptionsButton->setMenu(m_d->audioOptionsMenu); /********** Frame Editing Context Menu ***********************************************/ m_d->colorSelector = new KisColorLabelSelectorWidget(this); m_d->colorSelectorAction = new QWidgetAction(this); m_d->colorSelectorAction->setDefaultWidget(m_d->colorSelector); connect(m_d->colorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); m_d->multiframeColorSelector = new KisColorLabelSelectorWidget(this); m_d->multiframeColorSelectorAction = new QWidgetAction(this); m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector); connect(m_d->multiframeColorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); /********** Insert Keyframes Dialog **************************************************/ m_d->insertKeyframeDialog = new TimelineInsertKeyframeDialog(this); /********** Zoom Button **************************************************************/ m_d->zoomDragButton = new KisZoomButton(this); m_d->zoomDragButton->setAutoRaise(true); m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal")); m_d->zoomDragButton->setIconSize(QSize(20, 20)); // this icon is very small on windows if no explicitly set m_d->zoomDragButton->setToolTip(i18nc("@info:tooltip", "Zoom Timeline. Hold down and drag left or right.")); m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup); connect(m_d->zoomDragButton, SIGNAL(zoomLevelChanged(qreal)), SLOT(slotZoomButtonChanged(qreal))); connect(m_d->zoomDragButton, SIGNAL(zoomStarted(qreal)), SLOT(slotZoomButtonPressed(qreal))); setFramesPerSecond(12); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); { QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if( scroller ) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()), SLOT(slotSelectionChanged())); connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()), SLOT(slotUpdateFrameActions())); { QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(slotUpdateFrameActions())); } } TimelineFramesView::~TimelineFramesView() { } -void TimelineFramesView::setPinToTimeline(KisAction *action) -{ - m_d->pinLayerToTimelineAction = action; - m_d->layerEditingMenu->addAction(m_d->pinLayerToTimelineAction); -} - void TimelineFramesView::setActionManager(KisActionManager *actionManager) { m_d->actionMan = actionManager; m_d->horizontalRuler->setActionManager(actionManager); - if (actionManager) { KisAction *action = 0; action = m_d->actionMan->createAction("add_blank_frame"); connect(action, SIGNAL(triggered()), SLOT(slotAddBlankFrame())); action = m_d->actionMan->createAction("add_duplicate_frame"); connect(action, SIGNAL(triggered()), SLOT(slotAddDuplicateFrame())); action = m_d->actionMan->createAction("insert_keyframe_left"); connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframeLeft())); action = m_d->actionMan->createAction("insert_keyframe_right"); connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframeRight())); action = m_d->actionMan->createAction("insert_multiple_keyframes"); connect(action, SIGNAL(triggered()), SLOT(slotInsertMultipleKeyframes())); action = m_d->actionMan->createAction("remove_frames_and_pull"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveSelectedFramesAndShift())); action = m_d->actionMan->createAction("remove_frames"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveSelectedFrames())); action = m_d->actionMan->createAction("insert_hold_frame"); connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFrame())); action = m_d->actionMan->createAction("insert_multiple_hold_frames"); connect(action, SIGNAL(triggered()), SLOT(slotInsertMultipleHoldFrames())); action = m_d->actionMan->createAction("remove_hold_frame"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFrame())); action = m_d->actionMan->createAction("remove_multiple_hold_frames"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveMultipleHoldFrames())); action = m_d->actionMan->createAction("mirror_frames"); connect(action, SIGNAL(triggered()), SLOT(slotMirrorFrames())); action = m_d->actionMan->createAction("copy_frames_to_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotCopyFrames())); action = m_d->actionMan->createAction("cut_frames_to_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotCutFrames())); action = m_d->actionMan->createAction("paste_frames_from_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotPasteFrames())); action = m_d->actionMan->createAction("set_start_time"); connect(action, SIGNAL(triggered()), SLOT(slotSetStartTimeToCurrentPosition())); action = m_d->actionMan->createAction("set_end_time"); connect(action, SIGNAL(triggered()), SLOT(slotSetEndTimeToCurrentPosition())); action = m_d->actionMan->createAction("update_playback_range"); connect(action, SIGNAL(triggered()), SLOT(slotUpdatePlackbackRange())); + + action = m_d->actionMan->actionByName("pin_to_timeline"); + m_d->pinLayerToTimelineAction = action; + m_d->layerEditingMenu->addAction(action); } } void resizeToMinimalSize(QAbstractButton *w, int minimalSize) { QSize buttonSize = w->sizeHint(); if (buttonSize.height() > minimalSize) { buttonSize = QSize(minimalSize, minimalSize); } w->resize(buttonSize); } void TimelineFramesView::updateGeometries() { QTableView::updateGeometries(); const int availableHeight = m_d->horizontalRuler->height(); const int margin = 2; const int minimalSize = availableHeight - 2 * margin; resizeToMinimalSize(m_d->addLayersButton, minimalSize); resizeToMinimalSize(m_d->audioOptionsButton, minimalSize); resizeToMinimalSize(m_d->zoomDragButton, minimalSize); int x = 2 * margin; int y = (availableHeight - minimalSize) / 2; m_d->addLayersButton->move(x, 2 * y); m_d->audioOptionsButton->move(x + minimalSize + 2 * margin, 2 * y); const int availableWidth = m_d->layersHeader->width(); x = availableWidth - margin - minimalSize; m_d->zoomDragButton->move(x, 2 * y); } void TimelineFramesView::setModel(QAbstractItemModel *model) { TimelineFramesModel *framesModel = qobject_cast(model); m_d->model = framesModel; QTableView::setModel(model); connect(m_d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), this, SLOT(slotHeaderDataChanged(Qt::Orientation,int,int))); connect(m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); connect(m_d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotReselectCurrentIndex())); connect(m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()), this, SLOT(slotUpdateInfiniteFramesCount())); connect(m_d->model, SIGNAL(sigAudioChannelChanged()), this, SLOT(slotUpdateAudioActions())); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), &m_d->selectionChangedCompressor, SLOT(start())); connect(m_d->model, SIGNAL(sigEnsureRowVisible(int)), SLOT(slotEnsureRowVisible(int))); slotUpdateAudioActions(); } void TimelineFramesView::setFramesPerSecond(int fps) { m_d->fps = fps; m_d->horizontalRuler->setFramePerSecond(fps); // For some reason simple update sometimes doesn't work here, so // reset the whole header // // m_d->horizontalRuler->reset(); } void TimelineFramesView::slotZoomButtonPressed(qreal staticPoint) { m_d->zoomStillPointIndex = qIsNaN(staticPoint) ? currentIndex().column() : staticPoint; const int w = m_d->horizontalRuler->defaultSectionSize(); m_d->zoomStillPointOriginalOffset = w * m_d->zoomStillPointIndex - horizontalScrollBar()->value(); } void TimelineFramesView::slotZoomButtonChanged(qreal zoomLevel) { if (m_d->horizontalRuler->setZoom(zoomLevel)) { slotUpdateInfiniteFramesCount(); const int w = m_d->horizontalRuler->defaultSectionSize(); horizontalScrollBar()->setValue(w * m_d->zoomStillPointIndex - m_d->zoomStillPointOriginalOffset); viewport()->update(); } } void TimelineFramesView::slotColorLabelChanged(int label) { Q_FOREACH(QModelIndex index, selectedIndexes()) { m_d->model->setData(index, label, TimelineFramesModel::FrameColorLabelIndexRole); } KisImageConfig(false).setDefaultFrameColorLabel(label); } void TimelineFramesView::slotSelectAudioChannelFile() { if (!m_d->model) return; QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation); const QString currentFile = m_d->model->audioChannelFileName(); QDir baseDir = QFileInfo(currentFile).absoluteDir(); if (baseDir.exists()) { defaultDir = baseDir.absolutePath(); } const QString result = KisImportExportManager::askForAudioFileName(defaultDir, this); const QFileInfo info(result); if (info.exists()) { m_d->model->setAudioChannelFileName(info.absoluteFilePath()); } } void TimelineFramesView::slotAudioChannelMute(bool value) { if (!m_d->model) return; if (value != m_d->model->isAudioMuted()) { m_d->model->setAudioMuted(value); } } void TimelineFramesView::slotUpdateIcons() { m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none")); m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal")); } void TimelineFramesView::slotAudioChannelRemove() { if (!m_d->model) return; m_d->model->setAudioChannelFileName(QString()); } void TimelineFramesView::slotUpdateAudioActions() { if (!m_d->model) return; const QString currentFile = m_d->model->audioChannelFileName(); if (currentFile.isEmpty()) { m_d->openAudioAction->setText(i18nc("@item:inmenu", "Open audio...")); } else { QFileInfo info(currentFile); m_d->openAudioAction->setText(i18nc("@item:inmenu", "Change audio (%1)...", info.fileName())); } m_d->audioMuteAction->setChecked(m_d->model->isAudioMuted()); QIcon audioIcon; if (currentFile.isEmpty()) { audioIcon = KisIconUtils::loadIcon("audio-none"); } else { if (m_d->model->isAudioMuted()) { audioIcon = KisIconUtils::loadIcon("audio-volume-mute"); } else { audioIcon = KisIconUtils::loadIcon("audio-volume-high"); } } m_d->audioOptionsButton->setIcon(audioIcon); m_d->volumeSlider->setEnabled(!m_d->model->isAudioMuted()); KisSignalsBlocker b(m_d->volumeSlider); m_d->volumeSlider->setValue(qRound(m_d->model->audioVolume() * 100.0)); } void TimelineFramesView::slotAudioVolumeChanged(int value) { m_d->model->setAudioVolume(qreal(value) / 100.0); } void TimelineFramesView::slotUpdateInfiniteFramesCount() { if (horizontalScrollBar()->isSliderDown()) return; const int sectionWidth = m_d->horizontalRuler->defaultSectionSize(); const int calculatedIndex = (horizontalScrollBar()->value() + m_d->horizontalRuler->width() - 1) / sectionWidth; m_d->model->setLastVisibleFrame(calculatedIndex); } void TimelineFramesView::slotScrollerStateChanged( QScroller::State state ) { KisKineticScroller::updateCursor(this, state); } void TimelineFramesView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTableView::currentChanged(current, previous); if (previous.column() != current.column()) { m_d->model->setData(previous, false, TimelineFramesModel::ActiveFrameRole); m_d->model->setData(current, true, TimelineFramesModel::ActiveFrameRole); } } QItemSelectionModel::SelectionFlags TimelineFramesView::selectionCommand(const QModelIndex &index, const QEvent *event) const { // WARNING: Copy-pasted from KisNodeView! Please keep in sync! /** * Qt has a bug: when we Ctrl+click on an item, the item's * selections gets toggled on mouse *press*, whereas usually it is * done on mouse *release*. Therefore the user cannot do a * Ctrl+D&D with the default configuration. This code fixes the * problem by manually returning QItemSelectionModel::NoUpdate * flag when the user clicks on an item and returning * QItemSelectionModel::Toggle on release. */ if (event && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) && index.isValid()) { const QMouseEvent *mevent = static_cast(event); if (mevent->button() == Qt::RightButton && selectionModel()->selectedIndexes().contains(index)) { // Allow calling context menu for multiple layers return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonPress && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonRelease && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::Toggle; } } return QAbstractItemView::selectionCommand(index, event); } void TimelineFramesView::slotSelectionChanged() { int minColumn = std::numeric_limits::max(); int maxColumn = std::numeric_limits::min(); foreach (const QModelIndex &idx, selectedIndexes()) { if (idx.column() > maxColumn) { maxColumn = idx.column(); } if (idx.column() < minColumn) { minColumn = idx.column(); } } KisTimeRange range; if (maxColumn > minColumn) { range = KisTimeRange(minColumn, maxColumn - minColumn + 1); } + + if (m_d->model->isPlaybackPaused()) { + m_d->model->stopPlayback(); + } + m_d->model->setPlaybackRange(range); } void TimelineFramesView::slotReselectCurrentIndex() { QModelIndex index = currentIndex(); currentChanged(index, index); } void TimelineFramesView::slotEnsureRowVisible(int row) { QModelIndex index = currentIndex(); if (!index.isValid() || row < 0) return; index = m_d->model->index(row, index.column()); scrollTo(index); } void TimelineFramesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (m_d->model->isPlaybackActive()) return; int selectedColumn = -1; for (int j = topLeft.column(); j <= bottomRight.column(); j++) { QVariant value = m_d->model->data( m_d->model->index(topLeft.row(), j), TimelineFramesModel::ActiveFrameRole); if (value.isValid() && value.toBool()) { selectedColumn = j; break; } } QModelIndex index = currentIndex(); if (!index.isValid() && selectedColumn < 0) { return; } if (selectedColumn == -1) { selectedColumn = index.column(); } if (selectedColumn != index.column() && !m_d->dragInProgress) { int row= index.isValid() ? index.row() : 0; selectionModel()->setCurrentIndex(m_d->model->index(row, selectedColumn), QItemSelectionModel::ClearAndSelect); } } void TimelineFramesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_UNUSED(first); Q_UNUSED(last); if (orientation == Qt::Horizontal) { const int newFps = m_d->model->headerData(0, Qt::Horizontal, TimelineFramesModel::FramesPerSecondRole).toInt(); if (newFps != m_d->fps) { setFramesPerSecond(newFps); } } } void TimelineFramesView::rowsInserted(const QModelIndex& parent, int start, int end) { QTableView::rowsInserted(parent, start, end); } inline bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index) { return (model->flags(index) & Qt::ItemIsDragEnabled); } QStyleOptionViewItem TimelineFramesView::Private::viewOptionsV4() const { QStyleOptionViewItem option = q->viewOptions(); option.locale = q->locale(); option.locale.setNumberOptions(QLocale::OmitGroupSeparator); option.widget = q; return option; } QItemViewPaintPairs TimelineFramesView::Private::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QRect &rect = *r; const QRect viewportRect = q->viewport()->rect(); QItemViewPaintPairs ret; for (int i = 0; i < indexes.count(); ++i) { const QModelIndex &index = indexes.at(i); const QRect current = q->visualRect(index); if (current.intersects(viewportRect)) { ret += qMakePair(current, index); rect |= current; } } rect &= viewportRect; return ret; } QPixmap TimelineFramesView::Private::renderToPixmap(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r); if (paintPairs.isEmpty()) return QPixmap(); QPixmap pixmap(r->size()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); QStyleOptionViewItem option = viewOptionsV4(); option.state |= QStyle::State_Selected; for (int j = 0; j < paintPairs.count(); ++j) { option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; //adjustViewOptionsForIndex(&option, current); q->itemDelegate(current)->paint(&painter, option, current); } return pixmap; } void TimelineFramesView::startDrag(Qt::DropActions supportedActions) { QModelIndexList indexes = selectionModel()->selectedIndexes(); if (!indexes.isEmpty() && m_d->modifiersCatcher->modifierPressed("offset-frame")) { QVector rows; int leftmostColumn = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, indexes) { leftmostColumn = qMin(leftmostColumn, index.column()); if (!rows.contains(index.row())) { rows.append(index.row()); } } const int lastColumn = m_d->model->columnCount() - 1; selectionModel()->clear(); Q_FOREACH (const int row, rows) { QItemSelection sel(m_d->model->index(row, leftmostColumn), m_d->model->index(row, lastColumn)); selectionModel()->select(sel, QItemSelectionModel::Select); } supportedActions = Qt::MoveAction; { QModelIndexList indexes = selectedIndexes(); for(int i = indexes.count() - 1 ; i >= 0; --i) { if (!isIndexDragEnabled(m_d->model, indexes.at(i))) indexes.removeAt(i); } selectionModel()->clear(); if (indexes.count() > 0) { QMimeData *data = m_d->model->mimeData(indexes); if (!data) return; QRect rect; QPixmap pixmap = m_d->renderToPixmap(indexes, &rect); rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); drag->setMimeData(data); drag->setHotSpot(m_d->lastPressedPosition - rect.topLeft()); drag->exec(supportedActions, Qt::MoveAction); setCurrentIndex(currentIndex()); } } } else { /** * Workaround for Qt5's bug: if we start a dragging action right during * Shift-selection, Qt will get crazy. We cannot workaround it easily, * because we would need to fork mouseMoveEvent() for that (where the * decision about drag state is done). So we just abort dragging in that * case. * * BUG:373067 */ if (m_d->lastPressedModifier & Qt::ShiftModifier) { return; } /** * Workaround for Qt5's bugs: * * 1) Qt doesn't treat selection the selection on D&D * correctly, so we save it in advance and restore * afterwards. * * 2) There is a private variable in QAbstractItemView: * QAbstractItemView::Private::currentSelectionStartIndex. * It is initialized *only* when the setCurrentIndex() is called * explicitly on the view object, not on the selection model. * Therefore we should explicitly call setCurrentIndex() after * D&D, even if it already has *correct* value! * * 2) We should also call selectionModel()->select() * explicitly. There are two reasons for it: 1) Qt doesn't * maintain selection over D&D; 2) when reselecting single * element after D&D, Qt goes crazy, because it tries to * read *global* keyboard modifiers. Therefore if we are * dragging with Shift or Ctrl pressed it'll get crazy. So * just reset it explicitly. */ QModelIndexList selectionBefore = selectionModel()->selectedIndexes(); QModelIndex currentBefore = selectionModel()->currentIndex(); // initialize a global status variable m_d->dragWasSuccessful = false; QAbstractItemView::startDrag(supportedActions); QModelIndex newCurrent; QPoint selectionOffset; if (m_d->dragWasSuccessful) { newCurrent = currentIndex(); selectionOffset = QPoint(newCurrent.column() - currentBefore.column(), newCurrent.row() - currentBefore.row()); } else { newCurrent = currentBefore; selectionOffset = QPoint(); } setCurrentIndex(newCurrent); selectionModel()->clearSelection(); Q_FOREACH (const QModelIndex &idx, selectionBefore) { QModelIndex newIndex = model()->index(idx.row() + selectionOffset.y(), idx.column() + selectionOffset.x()); selectionModel()->select(newIndex, QItemSelectionModel::Select); } } } void TimelineFramesView::dragEnterEvent(QDragEnterEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragEnterEvent(event); } void TimelineFramesView::dragMoveEvent(QDragMoveEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragMoveEvent(event); if (event->isAccepted()) { QModelIndex index = indexAt(event->pos()); if (!m_d->model->canDropFrameData(event->mimeData(), index)) { event->ignore(); } else { selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); } } } void TimelineFramesView::dropEvent(QDropEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); if (event->keyboardModifiers() & Qt::ControlModifier) { event->setDropAction(Qt::CopyAction); } QAbstractItemView::dropEvent(event); m_d->dragWasSuccessful = event->isAccepted(); } void TimelineFramesView::dragLeaveEvent(QDragLeaveEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); QAbstractItemView::dragLeaveEvent(event); } void TimelineFramesView::createFrameEditingMenuActions(QMenu *menu, bool addFrameCreationActions) { slotUpdateFrameActions(); // calculate if selection range is set. This will determine if the update playback range is available QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); bool selectionExists = minColumn != maxColumn; menu->addSection(i18n("Edit Frames:")); menu->addSeparator(); if (selectionExists) { KisActionManager::safePopulateMenu(menu, "update_playback_range", m_d->actionMan); } else { KisActionManager::safePopulateMenu(menu, "set_start_time", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "set_end_time", m_d->actionMan); } menu->addSeparator(); KisActionManager::safePopulateMenu(menu, "cut_frames_to_clipboard", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "copy_frames_to_clipboard", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "paste_frames_from_clipboard", m_d->actionMan); menu->addSeparator(); - { // Tween submenu. - QMenu *frames = menu->addMenu(i18nc("@item:inmenu", "Tweening")); - - KisActionManager::safePopulateMenu(frames, "insert_opacity_keyframe", m_d->actionMan); - KisActionManager::safePopulateMenu(frames, "remove_opacity_keyframe", m_d->actionMan); - - // only allow to add an opacity keyframe if one doesn't exist - bool opacityKeyframeExists = model()->data(currentIndex(), TimelineFramesModel::SpecialKeyframeExists).toBool(); - m_d->actionMan->actionByName("insert_opacity_keyframe")->setEnabled(!opacityKeyframeExists); - m_d->actionMan->actionByName("remove_opacity_keyframe")->setEnabled(opacityKeyframeExists); - } - - { //Frames submenu. QMenu *frames = menu->addMenu(i18nc("@item:inmenu", "Keyframes")); KisActionManager::safePopulateMenu(frames, "insert_keyframe_left", m_d->actionMan); KisActionManager::safePopulateMenu(frames, "insert_keyframe_right", m_d->actionMan); frames->addSeparator(); KisActionManager::safePopulateMenu(frames, "insert_multiple_keyframes", m_d->actionMan); } { //Holds submenu. QMenu *hold = menu->addMenu(i18nc("@item:inmenu", "Hold Frames")); KisActionManager::safePopulateMenu(hold, "insert_hold_frame", m_d->actionMan); KisActionManager::safePopulateMenu(hold, "remove_hold_frame", m_d->actionMan); hold->addSeparator(); KisActionManager::safePopulateMenu(hold, "insert_multiple_hold_frames", m_d->actionMan); KisActionManager::safePopulateMenu(hold, "remove_multiple_hold_frames", m_d->actionMan); } menu->addSeparator(); KisActionManager::safePopulateMenu(menu, "remove_frames", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "remove_frames_and_pull", m_d->actionMan); menu->addSeparator(); if (addFrameCreationActions) { KisActionManager::safePopulateMenu(menu, "add_blank_frame", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "add_duplicate_frame", m_d->actionMan); menu->addSeparator(); } } void TimelineFramesView::mousePressEvent(QMouseEvent *event) { QPersistentModelIndex index = indexAt(event->pos()); if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (event->button() == Qt::RightButton) { // TODO: try calculate index under mouse cursor even when // it is outside any visible row qreal staticPoint = index.isValid() ? index.column() : currentIndex().column(); m_d->zoomDragButton->beginZoom(event->pos(), staticPoint); } else if (event->button() == Qt::LeftButton) { m_d->initialDragPanPos = event->pos(); m_d->initialDragPanValue = QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); } event->accept(); } else if (event->button() == Qt::RightButton) { int numSelectedItems = selectionModel()->selectedIndexes().size(); if (index.isValid() && numSelectedItems <= 1 && m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { model()->setData(index, true, TimelineFramesModel::ActiveLayerRole); model()->setData(index, true, TimelineFramesModel::ActiveFrameRole); setCurrentIndex(index); if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool() || model()->data(index, TimelineFramesModel::SpecialKeyframeExists).toBool()) { { KisSignalsBlocker b(m_d->colorSelector); QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0; m_d->colorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, false); menu.addSeparator(); menu.addAction(m_d->colorSelectorAction); menu.exec(event->globalPos()); } else { { KisSignalsBlocker b(m_d->colorSelector); const int labelIndex = KisImageConfig(true).defaultFrameColorLabel(); m_d->colorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, true); menu.addSeparator(); menu.addAction(m_d->colorSelectorAction); menu.exec(event->globalPos()); } } else if (numSelectedItems > 1) { int labelIndex = -1; bool haveFrames = false; Q_FOREACH(QModelIndex index, selectedIndexes()) { haveFrames |= index.data(TimelineFramesModel::FrameExistsRole).toBool(); QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); if (colorLabel.isValid()) { if (labelIndex == -1) { // First label labelIndex = colorLabel.toInt(); } else if (labelIndex != colorLabel.toInt()) { // Mixed colors in selection labelIndex = -1; break; } } } if (haveFrames) { KisSignalsBlocker b(m_d->multiframeColorSelector); m_d->multiframeColorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, false); menu.addSeparator(); KisActionManager::safePopulateMenu(&menu, "mirror_frames", m_d->actionMan); menu.addSeparator(); menu.addAction(m_d->multiframeColorSelectorAction); menu.exec(event->globalPos()); } } else if (event->button() == Qt::MidButton) { QModelIndex index = model()->buddy(indexAt(event->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, event->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); } event->accept(); } else { if (index.isValid()) { m_d->model->setLastClickedIndex(index); } m_d->lastPressedPosition = QPoint(horizontalOffset(), verticalOffset()) + event->pos(); m_d->lastPressedModifier = event->modifiers(); QAbstractItemView::mousePressEvent(event); } } void TimelineFramesView::mouseMoveEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (e->buttons() & Qt::RightButton) { m_d->zoomDragButton->continueZoom(e->pos()); } else if (e->buttons() & Qt::LeftButton) { QPoint diff = e->pos() - m_d->initialDragPanPos; QPoint offset = QPoint(m_d->initialDragPanValue.x() - diff.x(), m_d->initialDragPanValue.y() - diff.y()); const int height = m_d->layersHeader->defaultSectionSize(); horizontalScrollBar()->setValue(offset.x()); verticalScrollBar()->setValue(offset.y() / height); } e->accept(); } else if (e->buttons() == Qt::MidButton) { QModelIndex index = model()->buddy(indexAt(e->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, e->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); } e->accept(); } else { m_d->model->setScrubState(true); QTableView::mouseMoveEvent(e); } } void TimelineFramesView::mouseReleaseEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { e->accept(); } else { m_d->model->setScrubState(false); QTableView::mouseReleaseEvent(e); } } void TimelineFramesView::wheelEvent(QWheelEvent *e) { QModelIndex index = currentIndex(); int column= -1; if (index.isValid()) { column= index.column() + ((e->delta() > 0) ? 1 : -1); } if (column >= 0 && !m_d->dragInProgress) { setCurrentIndex(m_d->model->index(index.row(), column)); } } void TimelineFramesView::slotUpdateLayersMenu() { QAction *action = 0; m_d->existingLayersMenu->clear(); QVariant value = model()->headerData(0, Qt::Vertical, TimelineFramesModel::OtherLayersRole); if (value.isValid()) { TimelineFramesModel::OtherLayersList list = value.value(); int i = 0; Q_FOREACH (const TimelineFramesModel::OtherLayer &l, list) { action = m_d->existingLayersMenu->addAction(l.name); action->setData(i++); } } } void TimelineFramesView::slotUpdateFrameActions() { if (!m_d->actionMan) return; const QModelIndexList editableIndexes = calculateSelectionSpan(false, true); const bool hasEditableFrames = !editableIndexes.isEmpty(); bool hasExistingFrames = false; Q_FOREACH (const QModelIndex &index, editableIndexes) { if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool()) { hasExistingFrames = true; break; } } auto enableAction = [this] (const QString &id, bool value) { KisAction *action = m_d->actionMan->actionByName(id); KIS_SAFE_ASSERT_RECOVER_RETURN(action); action->setEnabled(value); }; enableAction("add_blank_frame", hasEditableFrames); enableAction("add_duplicate_frame", hasEditableFrames); enableAction("insert_keyframe_left", hasEditableFrames); enableAction("insert_keyframe_right", hasEditableFrames); enableAction("insert_multiple_keyframes", hasEditableFrames); enableAction("remove_frames", hasEditableFrames && hasExistingFrames); enableAction("remove_frames_and_pull", hasEditableFrames); enableAction("insert_hold_frame", hasEditableFrames); enableAction("insert_multiple_hold_frames", hasEditableFrames); enableAction("remove_hold_frame", hasEditableFrames); enableAction("remove_multiple_hold_frames", hasEditableFrames); enableAction("mirror_frames", hasEditableFrames && editableIndexes.size() > 1); enableAction("copy_frames_to_clipboard", true); enableAction("cut_frames_to_clipboard", hasEditableFrames); - - enableAction("insert_opacity_keyframe", hasEditableFrames); - enableAction("remove_opacity_keyframe", hasEditableFrames); - - //QClipboard *cp = QApplication::clipboard(); - //const QMimeData *data = cp->mimeData(); - - - //TODO: update column actions! } void TimelineFramesView::slotSetStartTimeToCurrentPosition() { m_d->model->setFullClipRangeStart(this->currentIndex().column()); } void TimelineFramesView::slotSetEndTimeToCurrentPosition() { m_d->model->setFullClipRangeEnd(this->currentIndex().column()); } void TimelineFramesView::slotUpdatePlackbackRange() { QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); m_d->model->setFullClipRangeStart(minColumn); m_d->model->setFullClipRangeEnd(maxColumn); } void TimelineFramesView::slotLayerContextMenuRequested(const QPoint &globalPos) { m_d->layerEditingMenu->exec(globalPos); } void TimelineFramesView::slotAddNewLayer() { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() : 0; model()->insertRow(newRow); } void TimelineFramesView::slotAddExistingLayer(QAction *action) { QVariant value = action->data(); if (value.isValid()) { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() + 1 : 0; m_d->model->insertOtherLayer(value.toInt(), newRow); } } void TimelineFramesView::slotRemoveLayer() { QModelIndex index = currentIndex(); if (!index.isValid()) return; model()->removeRow(index.row()); } void TimelineFramesView::slotAddBlankFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->createFrame(index); } void TimelineFramesView::slotAddDuplicateFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->copyFrame(index); } void TimelineFramesView::calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet &rows) const { minColumn = std::numeric_limits::max(); maxColumn = std::numeric_limits::min(); Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (!m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) continue; rows.insert(index.row()); minColumn = qMin(minColumn, index.column()); maxColumn = qMax(maxColumn, index.column()); } } void TimelineFramesView::insertKeyframes(int count, int timing, TimelineDirection direction, bool entireColumn) { QSet rows; int minColumn = 0, maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); if (count <= 0) { //Negative count? Use number of selected frames. count = qMax(1, maxColumn - minColumn + 1); } const int insertionColumn = direction == TimelineDirection::RIGHT ? maxColumn + 1 : minColumn; if (entireColumn) { rows.clear(); for (int i = 0; i < m_d->model->rowCount(); i++) { if (!m_d->model->data(m_d->model->index(i, insertionColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue; rows.insert(i); } } if (!rows.isEmpty()) { #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) m_d->model->insertFrames(insertionColumn, QList(rows.begin(), rows.end()), count, timing); #else m_d->model->insertFrames(insertionColumn, QList::fromSet(rows), count, timing); #endif } } void TimelineFramesView::insertMultipleKeyframes(bool entireColumn) { int count, timing; TimelineDirection direction; if (m_d->insertKeyframeDialog->promptUserSettings(count, timing, direction)) { insertKeyframes(count, timing, direction, entireColumn); } } QModelIndexList TimelineFramesView::calculateSelectionSpan(bool entireColumn, bool editableOnly) const { QModelIndexList indexes; if (entireColumn) { QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); rows.clear(); for (int i = 0; i < m_d->model->rowCount(); i++) { if (editableOnly && !m_d->model->data(m_d->model->index(i, minColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue; for (int column = minColumn; column <= maxColumn; column++) { indexes << m_d->model->index(i, column); } } } else { Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (!editableOnly || m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } return indexes; } void TimelineFramesView::slotRemoveSelectedFrames(bool entireColumn, bool pull) { const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn); if (!selectedIndices.isEmpty()) { if (pull) { m_d->model->removeFramesAndOffset(selectedIndices); } else { m_d->model->removeFrames(selectedIndices); } } } void TimelineFramesView::insertOrRemoveHoldFrames(int count, bool entireColumn) { QModelIndexList indexes; if (!entireColumn) { Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } else { const int column = selectionModel()->currentIndex().column(); for (int i = 0; i < m_d->model->rowCount(); i++) { const QModelIndex index = m_d->model->index(i, column); if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } if (!indexes.isEmpty()) { // add extra columns to the end of the timeline if we are adding hold frames // they will be truncated if we don't do this if (count > 0) { // Scan all the layers and find out what layer has the most keyframes // only keep a reference of layer that has the most keyframes int keyframesInLayerNode = 0; Q_FOREACH (const QModelIndex &index, indexes) { KisNodeSP layerNode = m_d->model->nodeAt(index); KisKeyframeChannel *channel = layerNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) continue; if (keyframesInLayerNode < channel->allKeyframeIds().count()) { keyframesInLayerNode = channel->allKeyframeIds().count(); } } m_d->model->setLastVisibleFrame(m_d->model->columnCount() + count*keyframesInLayerNode); } m_d->model->insertHoldFrames(indexes, count); // bulk adding frames can add too many // trim timeline to clean up extra frames that might have been added slotUpdateInfiniteFramesCount(); } } void TimelineFramesView::insertOrRemoveMultipleHoldFrames(bool insertion, bool entireColumn) { bool ok = false; const int count = QInputDialog::getInt(this, i18nc("@title:window", "Insert or Remove Hold Frames"), i18nc("@label:spinbox", "Enter number of frames"), insertion ? m_d->insertKeyframeDialog->defaultTimingOfAddedFrames() : m_d->insertKeyframeDialog->defaultNumberOfHoldFramesToRemove(), 1, 10000, 1, &ok); if (ok) { if (insertion) { m_d->insertKeyframeDialog->setDefaultTimingOfAddedFrames(count); insertOrRemoveHoldFrames(count, entireColumn); } else { m_d->insertKeyframeDialog->setDefaultNumberOfHoldFramesToRemove(count); insertOrRemoveHoldFrames(-count, entireColumn); } } } void TimelineFramesView::slotMirrorFrames(bool entireColumn) { const QModelIndexList indexes = calculateSelectionSpan(entireColumn); if (!indexes.isEmpty()) { m_d->model->mirrorFrames(indexes); } } void TimelineFramesView::cutCopyImpl(bool entireColumn, bool copy) { const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn, !copy); if (selectedIndices.isEmpty()) return; int minColumn = std::numeric_limits::max(); int minRow = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, selectedIndices) { minRow = qMin(minRow, index.row()); minColumn = qMin(minColumn, index.column()); } const QModelIndex baseIndex = m_d->model->index(minRow, minColumn); QMimeData *data = m_d->model->mimeDataExtended(selectedIndices, baseIndex, copy ? TimelineFramesModel::CopyFramesPolicy : TimelineFramesModel::MoveFramesPolicy); if (data) { QClipboard *cb = QApplication::clipboard(); cb->setMimeData(data); } } void TimelineFramesView::slotPasteFrames(bool entireColumn) { const QModelIndex currentIndex = !entireColumn ? this->currentIndex() : m_d->model->index(0, this->currentIndex().column()); if (!currentIndex.isValid()) return; QClipboard *cb = QApplication::clipboard(); const QMimeData *data = cb->mimeData(); if (data && data->hasFormat("application/x-krita-frame")) { bool dataMoved = false; bool result = m_d->model->dropMimeDataExtended(data, Qt::MoveAction, currentIndex, &dataMoved); if (result && dataMoved) { cb->clear(); } } } bool TimelineFramesView::viewportEvent(QEvent *event) { if (event->type() == QEvent::ToolTip && model()) { QHelpEvent *he = static_cast(event); QModelIndex index = model()->buddy(indexAt(he->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, he->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); return true; } } return QTableView::viewportEvent(event); } diff --git a/plugins/generators/pattern/kis_wdg_pattern.cpp b/plugins/generators/pattern/kis_wdg_pattern.cpp index 88b5e3ea9e..ae1297f242 100644 --- a/plugins/generators/pattern/kis_wdg_pattern.cpp +++ b/plugins/generators/pattern/kis_wdg_pattern.cpp @@ -1,162 +1,163 @@ /* * This file is part of Krita * * Copyright (c) 2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_wdg_pattern.h" #include #include #include #include #include #include #include #include #include #include "ui_wdgpatternoptions.h" KisWdgPattern::KisWdgPattern(QWidget* parent) : KisConfigWidget(parent) { m_widget = new Ui_WdgPatternOptions(); m_widget->setupUi(this); m_widget->lblPattern->setVisible(false); - m_widget->lblColor->setVisible(false); - m_widget->bnColor->setVisible(false); - - m_widget->sldShearX->setSuffix(i18n(" px")); - m_widget->sldShearY->setSuffix(i18n(" px")); - m_widget->sldShearX->setRange(-5.0, 5.0, 2); - m_widget->sldShearY->setRange(-5.0, 5.0, 2); - m_widget->sldShearX->setSingleStep(0.01); - m_widget->sldShearY->setSingleStep(0.01); + + m_widget->sldShearX->setSuffix(QChar(Qt::Key_Percent)); + m_widget->sldShearY->setSuffix(QChar(Qt::Key_Percent)); + m_widget->sldShearX->setRange(-500, 500, 2); + m_widget->sldShearY->setRange(-500, 500, 2); + m_widget->sldShearX->setSingleStep(1); + m_widget->sldShearY->setSingleStep(1); m_widget->sldShearX->setValue(0.0); m_widget->sldShearY->setValue(0.0); m_widget->spbOffsetX->setSuffix(i18n(" px")); m_widget->spbOffsetY->setSuffix(i18n(" px")); m_widget->spbOffsetX->setRange(-10000, 10000); m_widget->spbOffsetY->setRange(-10000, 10000); m_widget->sldRotationX->setSuffix(QChar(Qt::Key_degree)); m_widget->sldRotationY->setSuffix(QChar(Qt::Key_degree)); m_widget->sldRotationZ->setSuffix(QChar(Qt::Key_degree)); m_widget->sldRotationX->setRange(0.0, 360.0, 2); m_widget->sldRotationY->setRange(0.0, 360.0, 2); m_widget->sldRotationZ->setRange(0.0, 360.0, 2); m_widget->sldRotationX->setValue(0.0); m_widget->sldRotationY->setValue(0.0); m_widget->sldRotationZ->setValue(0.0); m_widget->sldRotationX->setSingleStep(1.0); m_widget->sldRotationY->setSingleStep(1.0); m_widget->sldRotationZ->setSingleStep(1.0); + + m_widget->gb3dRotation->setVisible(false); + connect(m_widget->patternChooser, SIGNAL(resourceSelected(KoResourceSP)), this, SIGNAL(sigConfigurationUpdated())); connect(m_widget->sldShearX, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); connect(m_widget->sldShearY, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); connect(m_widget->spbOffsetX, SIGNAL(valueChanged(int)), this, SIGNAL(sigConfigurationUpdated())); connect(m_widget->spbOffsetY, SIGNAL(valueChanged(int)), this, SIGNAL(sigConfigurationUpdated())); connect(m_widget->spbScaleWidth, SIGNAL(valueChanged(double)), this, SLOT(slotWidthChanged(double))); connect(m_widget->spbScaleHeight, SIGNAL(valueChanged(double)), this, SLOT(slotHeightChanged(double))); connect(m_widget->sldRotationX, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); connect(m_widget->sldRotationY, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); connect(m_widget->sldRotationZ, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); } KisWdgPattern::~KisWdgPattern() { delete m_widget; } void KisWdgPattern::setConfiguration(const KisPropertiesConfigurationSP config) { auto source = KisGlobalResourcesInterface::instance()->source(ResourceType::Patterns); KoPatternSP pattern = source.resourceForName(config->getString("pattern", "Grid01.pat")); widget()->patternChooser->setCurrentPattern(pattern ? pattern : source.fallbackResource()); m_widget->spbOffsetX->setValue(config->getInt("transform_offset_x", 0)); m_widget->spbOffsetY->setValue(config->getInt("transform_offset_y", 0)); - m_widget->spbScaleWidth->setValue(config->getInt("transform_scale_x", 1.0)*100); - m_widget->spbScaleHeight->setValue(config->getInt("transform_scale_y", 1.0)*100); + m_widget->spbScaleWidth->setValue(config->getInt("transform_scale_x", 1.0) * 100); + m_widget->spbScaleHeight->setValue(config->getInt("transform_scale_y", 1.0) * 100); m_widget->btnLockAspectRatio->setKeepAspectRatio(config->getBool("transform_keep_scale_aspect", true)); - m_widget->sldShearX->setValue(config->getDouble("transform_shear_x", 0.0)); - m_widget->sldShearY->setValue(config->getDouble("transform_shear_y", 0.0)); + m_widget->sldShearX->setValue(config->getDouble("transform_shear_x", 0.0) * 100); + m_widget->sldShearY->setValue(config->getDouble("transform_shear_y", 0.0) * 100); widget()->sldRotationX->setValue(config->getDouble("transform_rotation_x", 0.0)); widget()->sldRotationY->setValue(config->getDouble("transform_rotation_y", 0.0)); widget()->sldRotationZ->setValue(config->getDouble("transform_rotation_z", 0.0)); } KisPropertiesConfigurationSP KisWdgPattern::configuration() const { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); KisFilterConfigurationSP config = generator->factoryConfiguration(KisGlobalResourcesInterface::instance()); QVariant v; if (widget()->patternChooser->currentResource()) { v.setValue(widget()->patternChooser->currentResource()->name()); config->setProperty("pattern", v); } config->setProperty("transform_offset_x", m_widget->spbOffsetX->value()); config->setProperty("transform_offset_y", m_widget->spbOffsetY->value()); - config->setProperty("transform_scale_x", m_widget->spbScaleWidth->value()/100); - config->setProperty("transform_scale_y", m_widget->spbScaleHeight->value()/100); + config->setProperty("transform_scale_x", m_widget->spbScaleWidth->value() / 100); + config->setProperty("transform_scale_y", m_widget->spbScaleHeight->value() / 100); config->setProperty("transform_keep_scale_aspect", m_widget->btnLockAspectRatio->keepAspectRatio()); - config->setProperty("transform_shear_x", widget()->sldShearX->value()); - config->setProperty("transform_shear_y", widget()->sldShearY->value()); + config->setProperty("transform_shear_x", widget()->sldShearX->value() / 100); + config->setProperty("transform_shear_y", widget()->sldShearY->value() / 100); config->setProperty("transform_rotation_x", widget()->sldRotationX->value()); config->setProperty("transform_rotation_y", widget()->sldRotationY->value()); config->setProperty("transform_rotation_z", widget()->sldRotationZ->value()); return config; } void KisWdgPattern::slotWidthChanged(double w) { if (m_widget->btnLockAspectRatio->keepAspectRatio()) { m_widget->spbScaleHeight->blockSignals(true); m_widget->spbScaleHeight->setValue(w); m_widget->spbScaleHeight->blockSignals(false); } emit sigConfigurationUpdated(); } void KisWdgPattern::slotHeightChanged(double h) { if (m_widget->btnLockAspectRatio->keepAspectRatio()) { m_widget->spbScaleWidth->blockSignals(true); m_widget->spbScaleWidth->setValue(h); m_widget->spbScaleWidth->blockSignals(false); } emit sigConfigurationUpdated(); } diff --git a/plugins/generators/pattern/wdgpatternoptions.ui b/plugins/generators/pattern/wdgpatternoptions.ui index b847dee948..5e35c950dc 100644 --- a/plugins/generators/pattern/wdgpatternoptions.ui +++ b/plugins/generators/pattern/wdgpatternoptions.ui @@ -1,304 +1,283 @@ WdgPatternOptions 0 0 484 477 0 Pattern &Pattern: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter patternChooser 0 0 - - - - &Color: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - bnColor - - - - - - Transform - + 3d Rotation: X-Rotation: Y-Rotation: 0 0 0 0 - + Scale: Width: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter % 1 500.000000000000000 100.000000000000000 Height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter % 1 500.000000000000000 100.000000000000000 - + Offset: X: Y: - + Shear: X: Y: 0 0 0 0 - + Rotation: - - KisColorButton - QPushButton -
kis_color_button.h
-
KisPatternChooser QWidget
kis_pattern_chooser.h
1
KoAspectButton QWidget
KoAspectButton.h
1
KisDoubleParseSpinBox QDoubleSpinBox
kis_double_parse_spin_box.h
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
diff --git a/plugins/python/batch_exporter/COATools.py b/plugins/python/batch_exporter/COATools.py index 02214ed5cd..8820a16d72 100644 --- a/plugins/python/batch_exporter/COATools.py +++ b/plugins/python/batch_exporter/COATools.py @@ -1,99 +1,92 @@ -import os import json +from pathlib import Path class COAToolsFormat: def __init__(self, cfg, statusBar): self.cfg = cfg self.statusBar = statusBar self.reset() def reset(self): self.nodes = [] def showError(self, msg): msg, timeout = (self.cfg["error"]["msg"].format(msg), self.cfg["error"]["timeout"]) self.statusBar.showMessage(msg, timeout) def collect(self, node): print("COAToolsFormat collecting %s" % (node.name)) self.nodes.append(node) def remap(self, oldValue, oldMin, oldMax, newMin, newMax): if oldMin == newMin and oldMax == newMax: return oldValue return (((oldValue - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin def save(self, output_dir=""): """ Parses layers configured to export to COA Tools and builds the JSON data COA Tools need to import the files """ # For each top-level node (Group Layer) - cfg = self.cfg export_dir = output_dir for wn in self.nodes: children = wn.children path = wn.path if path != "": export_dir = path print("COAToolsFormat exporting %d items from %s" % (len(children), wn.name)) try: if len(children) <= 0: raise ValueError(wn.name, "has no children to export") coa_data = {"name": wn.name, "nodes": []} print("COAToolsFormat exporting %s to %s" % (wn.name, export_dir)) for idx, child in enumerate(children): sheet_meta = dict() if child.coa != "": fn, sheet_meta = child.saveCOASpriteSheet(export_dir) else: fn = child.saveCOA(export_dir) - + path = Path(fn) node = child.node coords = node.bounds().getCoords() relative_coords = coords parent_node = node.parentNode() parent_coords = parent_node.bounds().getCoords() relative_coords = [coords[0] - parent_coords[0], coords[1] - parent_coords[1]] p_width = parent_coords[2] - parent_coords[0] p_height = parent_coords[3] - parent_coords[1] tiles_x, tiles_y = 1, 1 if len(sheet_meta) > 0: tiles_x, tiles_y = sheet_meta["tiles_x"], sheet_meta["tiles_y"] coa_entry = { "children": [], "frame_index": 0, "name": child.name, "node_path": child.name, "offset": [-p_width / 2, p_height / 2], "opacity": self.remap(node.opacity(), 0, 255, 0, 1), "pivot_offset": [0.0, 0.0], "position": relative_coords, - "resource_path": fn.replace( - export_dir + os.path.sep + cfg["outDir"] + os.path.sep, "" - ), + "resource_path": str(path.relative_to(Path(export_dir))), "rotation": 0.0, "scale": [1.0, 1.0], "tiles_x": tiles_x, "tiles_y": tiles_y, "type": "SPRITE", "z": idx - len(children) + 1, } coa_data["nodes"].append(coa_entry) - json_data = json.dumps(coa_data, sort_keys=True, indent=4, separators=(",", ": ")) - with open( - export_dir + os.path.sep + cfg["outDir"] + os.path.sep + wn.name + ".json", "w" - ) as fh: - fh.write(json_data) + Path(export_dir, wn.name + ".json").write_text(json_data) except ValueError as e: self.showError(e) diff --git a/plugins/python/batch_exporter/Config.py b/plugins/python/batch_exporter/Config.py index a7b31281dc..a77087679d 100644 --- a/plugins/python/batch_exporter/Config.py +++ b/plugins/python/batch_exporter/Config.py @@ -1,15 +1,15 @@ import re from collections import OrderedDict CONFIG = { "outDir": "export", - "rootPat": r"^root", + "rootPat": "root", "sym": r"[^a-zA-Z0-9_-]", "error": {"msg": "ERROR: {}", "timeout": 8000}, "done": {"msg": "DONE: {}", "timeout": 5000}, "delimiters": OrderedDict((("assign", "="), ("separator", ","))), # yapf: disable "meta": {"c": [""], "e": ["png"], "m": [0], "p": [""], "s": [100]}, } CONFIG["rootPat"] = re.compile(CONFIG["rootPat"]) CONFIG["sym"] = re.compile(CONFIG["sym"]) diff --git a/plugins/python/batch_exporter/Utils/Export.py b/plugins/python/batch_exporter/Utils/Export.py index 0867267a7b..d93352c61a 100644 --- a/plugins/python/batch_exporter/Utils/Export.py +++ b/plugins/python/batch_exporter/Utils/Export.py @@ -1,19 +1,27 @@ -import os import re +from pathlib import Path + from ..Config import CONFIG def exportPath(cfg, path, dirname=""): - return os.path.join(dirname, subRoot(cfg, path)) + return dirname / subRoot(cfg, path) def subRoot(cfg, path): patF, patR = cfg["rootPat"], CONFIG["outDir"] - return re.sub(patF, patR, path, count=1) + original = Path(path) + rootless = ( + original.relateive_to(patF) + if original.parents and original.parents[0] == patF + else original + ) + return patR / rootless + + +_sanitizer_re = re.compile(CONFIG["sym"]) def sanitize(path): - ps = path.split(os.path.sep) - ps = map(lambda p: re.sub(CONFIG["sym"], "_", p), ps) - ps = os.path.sep.join(ps) - return ps + ps = map(lambda p: _sanitizer_re.sub("_", p), Path(path).parts) + return str(Path(*ps)) diff --git a/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop b/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop index f5f80cedf1..5b5e8e1e58 100644 --- a/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop +++ b/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop @@ -1,26 +1,30 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=batch_exporter X-Krita-Manual=Manual.html X-Python-2-Compatible=false Name=Batch Exporter Name[ca]=Exportador per lots +Name[en_GB]=Batch Exporter Name[es]=Exportador por lotes Name[et]=Hulgieksport Name[nl]=Exportprogramma voor bulk Name[nn]=Fleirbileteksport Name[pt]=Exportação em Lote Name[pt_BR]=Exportador em lote +Name[sv]=Bakgrundsexport Name[uk]=Пакетне експортування Name[x-test]=xxBatch Exporterxx Comment=Smart export tool that uses layer names to scale and (re-)export art assets in batches fast Comment[ca]=Eina d'exportació intel·ligent que usa els noms de les capes per escalar i (re)exportar elements d'art en lots ràpidament +Comment[en_GB]=Smart export tool that uses layer names to scale and (re-)export art assets in batches fast Comment[es]=Herramienta de exportación inteligente que usa nombres de capas para escalar y (volver a) exportar recursos artísticos por lotes de un modo rápido Comment[et]=Nutikas eksporditööriist, mid kasutab kihtide nimesid kunstiresursside kiireks hulgiskaleerimiseks ja -(taas)eksportimiseks Comment[nl]=Slim hulpmiddel voor exporteren die lagennamen gebruikt om snel kunstbezittingen te schalen en (opnieuw) te exporteren in bulk Comment[nn]=Smart eksportverktøy som brukar lagnamn til å skalera og (re)eksportera til fleire bilete Comment[pt]=Uma ferramenta inteligente de exportação em lote que usa os nomes das camadas para definir a escala e (re-)exportar os itens gráficos em lote de forma rápida Comment[pt_BR]=Ferramenta de exportação inteligente que usa nomes de camada para dimensionar e (re)exportar ativos de arte em lotes rapidamente +Comment[sv]=Smart exportverktyg som använder lagernamn för att snabbt skala och exportera om grafiktillgångar i bakgrunden Comment[uk]=Інструмент пакетного експортування, який використовує назви шарів для масштабування і швидкого (повторного) пакетного експортування художніх елементів Comment[x-test]=xxSmart export tool that uses layer names to scale and (re-)export art assets in batches fastxx diff --git a/plugins/python/batch_exporter/pyproject.toml b/plugins/python/batch_exporter/pyproject.toml index 440365c381..e51f549ffe 100644 --- a/plugins/python/batch_exporter/pyproject.toml +++ b/plugins/python/batch_exporter/pyproject.toml @@ -1,9 +1,4 @@ [tool.black] line-length=100 include = '\.py$' -exclude = ''' -/( - \.git - | Dependencies -)/ -''' \ No newline at end of file +exclude = '/(\.git)/' diff --git a/plugins/python/channels2layers/channels2layers.py b/plugins/python/channels2layers/channels2layers.py index c03dcdb893..bab499c952 100644 --- a/plugins/python/channels2layers/channels2layers.py +++ b/plugins/python/channels2layers/channels2layers.py @@ -1,1461 +1,1461 @@ #----------------------------------------------------------------------------- # Channels to Layers # Copyright (C) 2019 - Grum999 # ----------------------------------------------------------------------------- # 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 3 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, see https://www.gnu.org/licenses/ # ----------------------------------------------------------------------------- # A Krita plugin designed to split channels from a layer to sub-layers # . RGB # . CMY # . CMYK # . RGB as greayscale values # . CMY as greayscale values # . CMYK as greayscale values # ----------------------------------------------------------------------------- import re from krita import ( Extension, InfoObject, Node, Selection ) from PyQt5.Qt import * from PyQt5 import QtCore from PyQt5.QtCore import ( pyqtSlot, QBuffer, QByteArray, QIODevice ) from PyQt5.QtGui import ( QColor, QImage, QPixmap, ) from PyQt5.QtWidgets import ( QApplication, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFormLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QProgressBar, QProgressDialog, QVBoxLayout, QWidget ) PLUGIN_VERSION = '1.1.0' EXTENSION_ID = 'pykrita_channels2layers' PLUGIN_MENU_ENTRY = i18n('Channels to layers') PLUGIN_DIALOG_TITLE = "{0} - {1}".format(i18n('Channels to layers'), PLUGIN_VERSION) # Define DialogBox types DBOX_INFO = 'i' DBOX_WARNING ='w' # Define Output modes OUTPUT_MODE_RGB = i18n('RGB Colors') OUTPUT_MODE_CMY = i18n('CMY Colors') OUTPUT_MODE_CMYK = i18n('CMYK Colors') OUTPUT_MODE_LRGB = i18n('RGB Grayscale levels') OUTPUT_MODE_LCMY = i18n('CMY Grayscale levels') OUTPUT_MODE_LCMYK = i18n('CMYK Grayscale levels') OUTPUT_PREVIEW_MAXSIZE = 320 # Define original layer action ORIGINAL_LAYER_KEEPUNCHANGED = i18n('Unchanged') ORIGINAL_LAYER_KEEPVISIBLE = i18n('Visible') ORIGINAL_LAYER_KEEPHIDDEN = i18n('Hidden') ORIGINAL_LAYER_REMOVE = i18n('Remove') # define dialog option minimum dimension DOPT_MIN_WIDTH = OUTPUT_PREVIEW_MAXSIZE * 5 + 200 DOPT_MIN_HEIGHT = 480 OUTPUT_MODE_NFO = { OUTPUT_MODE_RGB : { 'description' : 'Extract channels (Red, Green, Blue) and create a colored layer per channel', 'groupLayerName' : 'RGB', 'layers' : [ { 'color' : 'B', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.blue) } }, { 'action' : 'blending mode', 'value' : 'inverse_subtract' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' } ] }, { 'color' : 'G', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.green) } }, { 'action' : 'blending mode', 'value' : 'inverse_subtract' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' } ] }, { 'color' : 'R', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.red) } }, { 'action' : 'blending mode', 'value' : 'inverse_subtract' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' } ] } ] }, OUTPUT_MODE_CMY : { 'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a colored layer per channel', 'groupLayerName' : 'CMY', 'layers' : [ { 'color' : 'Y', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.yellow) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' } ] }, { 'color' : 'M', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.magenta) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' } ] }, { 'color' : 'C', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.cyan) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' } ] } ] }, OUTPUT_MODE_CMYK : { 'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a colored layer per channel', 'groupLayerName' : 'CMYK', 'layers' : [ { 'color' : 'K', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=5' # desaturate method = max } ] }, { 'color' : 'Y', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.yellow) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'duplicate', 'value' : '@K' }, { 'action' : 'blending mode', 'value' : 'divide' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' } ] }, { 'color' : 'M', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.magenta) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'duplicate', 'value' : '@K' }, { 'action' : 'blending mode', 'value' : 'divide' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' } ] }, { 'color' : 'C', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.cyan) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'duplicate', 'value' : '@K' }, { 'action' : 'blending mode', 'value' : 'divide' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' } ] } ] }, OUTPUT_MODE_LRGB : { 'description' : 'Extract channels (Red, Green, Blue) and create a grayscale layer per channel', 'groupLayerName' : 'RGB[GS]', 'layers' : [ { 'color' : 'B', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.blue) } }, { 'action' : 'blending mode', 'value' : 'inverse_subtract' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=5' # desaturate method = max } ] }, { 'color' : 'G', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.green) } }, { 'action' : 'blending mode', 'value' : 'inverse_subtract' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=5' # desaturate method = max } ] }, { 'color' : 'R', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.red) } }, { 'action' : 'blending mode', 'value' : 'inverse_subtract' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=5' # desaturate method = max } ] } ] }, OUTPUT_MODE_LCMY : { 'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a grayscale layer per channel', 'groupLayerName' : 'CMY[GS]', 'layers' : [ { 'color' : 'Y', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.yellow) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=4' # desaturate method = min } ] }, { 'color' : 'M', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.magenta) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=4' # desaturate method = min } ] }, { 'color' : 'C', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.cyan) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=4' # desaturate method = min } ] } ] }, OUTPUT_MODE_LCMYK : { 'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a grayscale layer per channel', 'groupLayerName' : 'CMYK[GS]', 'layers' : [ { 'color' : 'K', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=5' # desaturate method = max } ] }, { 'color' : 'Y', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.yellow) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'duplicate', 'value' : '@K' }, { 'action' : 'blending mode', 'value' : 'divide' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=4' # desaturate method = min } ] }, { 'color' : 'M', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.magenta) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'duplicate', 'value' : '@K' }, { 'action' : 'blending mode', 'value' : 'divide' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=4' # desaturate method = min } ] }, { 'color' : 'C', 'process': [ { 'action' : 'duplicate', 'value' : '@original' }, { 'action' : 'new', 'value' : { 'type' : 'filllayer', 'color' : QColor(Qt.cyan) } }, { 'action' : 'blending mode', 'value' : 'add' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'duplicate', 'value' : '@K' }, { 'action' : 'blending mode', 'value' : 'divide' }, { 'action' : 'merge down', 'value' : None }, { 'action' : 'blending mode', 'value' : 'multiply' }, { 'action' : 'filter', 'value' : 'name=desaturate;type=4' # desaturate method = min } ] } ] } } TRANSLATIONS_DICT = { 'colorDepth' : { 'U8' : '8bits', 'U16' : '16bits', 'F16' : '16bits floating point', 'F32' : '32bits floating point' }, 'colorModel' : { 'A' : 'Alpha mask', 'RGBA' : 'RGB with alpha channel', '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' }, 'layerType' : { 'paintlayer' : 'Paint layer', 'grouplayer' : 'Group layer', 'filelayer' : 'File layer', 'filterlayer' : 'Filter layer', 'filllayer' : 'Fill layer', 'clonelayer' : 'Clone layer', 'vectorlayer' : 'Vector layer', 'transparencymask' : 'Transparency mask', 'filtermask' : 'Filter mask', 'transformmask': 'Transform mask', 'selectionmask': 'Selection mask', 'colorizemask' : 'Colorize mask' } } class ChannelsToLayers(Extension): def __init__(self, parent): # Default options self.__outputOptions = { 'outputMode': OUTPUT_MODE_RGB, 'originalLayerAction': ORIGINAL_LAYER_KEEPHIDDEN, 'layerGroupName': '{mode}-{source:name}', 'layerColorName': '{mode}[{color:short}]-{source:name}' } self.__sourceDocument = None self.__sourceLayer = None # Always initialise the superclass. # This is necessary to create the underlying C++ object super().__init__(parent) self.parent = parent def setup(self): pass def createActions(self, window): action = window.createAction(EXTENSION_ID, PLUGIN_MENU_ENTRY, "tools/scripts") action.triggered.connect(self.action_triggered) def dBoxMessage(self, msgType, msg): """Simplified function for DialogBox 'OK' message""" if msgType == DBOX_WARNING: QMessageBox.warning( QWidget(), PLUGIN_DIALOG_TITLE, i18n(msg) ) else: QMessageBox.information( QWidget(), PLUGIN_DIALOG_TITLE, i18n(msg) ) def action_triggered(self): """Action called when script is executed from Kitra menu""" if self.checkCurrentLayer(): if self.openDialogOptions(): self.run() def translateDictKey(self, key, value): """Translate key from dictionnary (mostly internal Krita internal values) to human readable values""" returned = i18n('Unknown') if key in TRANSLATIONS_DICT.keys(): if value in TRANSLATIONS_DICT[key].keys(): returned = i18n(TRANSLATIONS_DICT[key][value]) return returned def checkCurrentLayer(self): """Check if current layer is valid - A document must be opened - Active layer properties must be: . Layer type: a paint layer . Color model: RGBA . Color depth: 8bits """ self.__sourceDocument = Application.activeDocument() # Check if there's an active document if self.__sourceDocument is None: self.dBoxMessage(DBOX_WARNING, "There's no active document!") return False self.__sourceLayer = self.__sourceDocument.activeNode() # Check if current layer can be processed if self.__sourceLayer.type() != "paintlayer" or self.__sourceLayer.colorModel() != "RGBA" or self.__sourceLayer.colorDepth() != "U8": self.dBoxMessage(DBOX_WARNING, "Selected layer must be a 8bits RGBA Paint Layer!" "\n\nCurrent layer '{0}' properties:" "\n- Layer type: {1}" "\n- Color model: {2} ({3})" "\n- Color depth: {4}" "\n\n> Action is cancelled".format(self.__sourceLayer.name(), self.translateDictKey('layerType', self.__sourceLayer.type()), self.__sourceLayer.colorModel(), self.translateDictKey('colorModel', self.__sourceLayer.colorModel()), self.translateDictKey('colorDepth', self.__sourceLayer.colorDepth()) )) return False return True def toQImage(self, layerNode, rect=None): """Return `layerNode` content as a QImage (as ARGB32) The `rect` value can be: - None, in this case will return all `layerNode` content - A QRect() object, in this case return `layerNode` content reduced to given rectangle bounds """ srcRect = layerNode.bounds() if len(layerNode.childNodes()) == 0: projectionMode = False else: projectionMode = True if projectionMode == True: img = QImage(layerNode.projectionPixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format_ARGB32) else: img = QImage(layerNode.pixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format_ARGB32) return img.scaled(rect.width(), rect.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) def openDialogOptions(self): """Open dialog box to let user define channel extraction options""" tmpDocument = None previewBaSrc = QByteArray() lblPreview = [QLabel(), QLabel(), QLabel(), QLabel()] lblPreviewLbl = [QLabel(), QLabel(), QLabel(), QLabel()] # ---------------------------------------------------------------------- # Define signal and slots for UI widgets @pyqtSlot('QString') def ledLayerGroupName_Changed(value): self.__outputOptions['layerGroupName'] = value @pyqtSlot('QString') def ledLayerColorName_Changed(value): self.__outputOptions['layerColorName'] = value @pyqtSlot('QString') def cmbOutputMode_Changed(value): self.__outputOptions['outputMode'] = value buildPreview() @pyqtSlot('QString') def cmbOriginalLayerAction_Changed(value): self.__outputOptions['originalLayerAction'] = value def buildPreview(): pbProgress.setVisible(True) backupValue = self.__outputOptions['layerColorName'] self.__outputOptions['layerColorName'] = '{color:long}' # create a temporary document to work tmpDocument = Application.createDocument(imgThumbSrc.width(), imgThumbSrc.height(), "tmp", "RGBA", "U8", "", 120.0) # create a layer used as original layer originalLayer = tmpDocument.createNode("Original", "paintlayer") tmpDocument.rootNode().addChildNode(originalLayer, None) # and set original image content originalLayer.setPixelData(previewBaSrc, 0, 0, tmpDocument.width(), tmpDocument.height()) # execute process groupLayer = self.process(tmpDocument, originalLayer, pbProgress) self.__outputOptions['layerColorName'] = backupValue originalLayer.setVisible(False) groupLayer.setVisible(True) for layer in groupLayer.childNodes(): layer.setBlendingMode('normal') layer.setVisible(False) tmpDocument.refreshProjection() index = 0 for layer in groupLayer.childNodes(): layer.setVisible(True) tmpDocument.refreshProjection() lblPreview[index].setPixmap(QPixmap.fromImage(tmpDocument.projection(0, 0, tmpDocument.width(), tmpDocument.height()))) lblPreviewLbl[index].setText("{0}".format(layer.name())) layer.setVisible(False) index+=1 if index > 3: lblPreview[3].setVisible(True) lblPreviewLbl[3].setVisible(True) else: lblPreview[3].setVisible(False) lblPreviewLbl[3].setVisible(False) tmpDocument.close() pbProgress.setVisible(False) # ---------------------------------------------------------------------- # Create dialog box dlgMain = QDialog(Application.activeWindow().qwindow()) dlgMain.setWindowTitle(PLUGIN_DIALOG_TITLE) # resizeable with minimum size dlgMain.setSizeGripEnabled(True) dlgMain.setMinimumSize(DOPT_MIN_WIDTH, DOPT_MIN_HEIGHT) dlgMain.setModal(True) # ...................................................................... # main dialog box, container vbxMainContainer = QVBoxLayout(dlgMain) # main dialog box, current layer name lblLayerName = QLabel("{0} {1}".format(i18n("Processing layer"), self.__sourceLayer.name())) lblLayerName.setFixedHeight(30) vbxMainContainer.addWidget(lblLayerName) # main dialog box, groupbox for layers options gbxLayersMgt = QGroupBox("Layers management") vbxMainContainer.addWidget(gbxLayersMgt) # main dialog box, groupbox for output options gbxOutputResults = QGroupBox("Output results") vbxMainContainer.addWidget(gbxOutputResults) vbxMainContainer.addStretch() # main dialog box, OK/Cancel buttons dbbxOkCancel = QDialogButtonBox(dlgMain) dbbxOkCancel.setOrientation(QtCore.Qt.Horizontal) dbbxOkCancel.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) dbbxOkCancel.accepted.connect(dlgMain.accept) dbbxOkCancel.rejected.connect(dlgMain.reject) vbxMainContainer.addWidget(dbbxOkCancel) # ...................................................................... # create layout for groupbox "Layers management" flLayersMgt = QFormLayout() gbxLayersMgt.setLayout(flLayersMgt) ledLayerGroupName = QLineEdit() ledLayerGroupName.setText(self.__outputOptions['layerGroupName']) ledLayerGroupName.textChanged.connect(ledLayerGroupName_Changed) flLayersMgt.addRow(i18nc('The name for a new group layer; the generated layers will be placed in this group.', 'New layer group name'), ledLayerGroupName) ledLayerColorName = QLineEdit() ledLayerColorName.setText(self.__outputOptions['layerColorName']) ledLayerColorName.textChanged.connect(ledLayerColorName_Changed) flLayersMgt.addRow(i18nc('Defines how the name for each layer created from the channel is generated.', 'New layers color name'), ledLayerColorName) cmbOriginalLayerAction = QComboBox() cmbOriginalLayerAction.addItems([ ORIGINAL_LAYER_KEEPUNCHANGED, ORIGINAL_LAYER_KEEPVISIBLE, ORIGINAL_LAYER_KEEPHIDDEN, ORIGINAL_LAYER_REMOVE ]) cmbOriginalLayerAction.setCurrentText(self.__outputOptions['originalLayerAction']) cmbOriginalLayerAction.currentTextChanged.connect(cmbOriginalLayerAction_Changed) flLayersMgt.addRow(i18n("Original layer"), cmbOriginalLayerAction) # ...................................................................... # create layout for groupbox "Output results" flOutputResults = QFormLayout() gbxOutputResults.setLayout(flOutputResults) cmbOutputMode = QComboBox() cmbOutputMode.addItems([ OUTPUT_MODE_RGB, OUTPUT_MODE_CMY, OUTPUT_MODE_CMYK, OUTPUT_MODE_LRGB, OUTPUT_MODE_LCMY, OUTPUT_MODE_LCMYK ]) cmbOutputMode.setCurrentText(self.__outputOptions['outputMode']) cmbOutputMode.currentTextChanged.connect(cmbOutputMode_Changed) flOutputResults.addRow(i18n("Mode"), cmbOutputMode) vbxPreviewLblContainer = QHBoxLayout() flOutputResults.addRow('', vbxPreviewLblContainer) vbxPreviewContainer = QHBoxLayout() flOutputResults.addRow('', vbxPreviewContainer) # add preview progressbar pbProgress = QProgressBar() pbProgress.setFixedHeight(8) pbProgress.setTextVisible(False) pbProgress.setVisible(False) pbProgress.setRange(0, 107) flOutputResults.addRow('', pbProgress) imageRatio = self.__sourceDocument.width() / self.__sourceDocument.height() rect = QRect(0, 0, OUTPUT_PREVIEW_MAXSIZE, OUTPUT_PREVIEW_MAXSIZE) # always ensure that final preview width and/or height is lower or equal than OUTPUT_PREVIEW_MAXSIZE if imageRatio < 1: # width < height rect.setWidth(int(imageRatio * OUTPUT_PREVIEW_MAXSIZE)) else: # width >= height rect.setHeight(int(OUTPUT_PREVIEW_MAXSIZE / imageRatio)) imgThumbSrc = self.toQImage(self.__sourceLayer, rect) previewBaSrc.resize(imgThumbSrc.byteCount()) ptr = imgThumbSrc.bits() ptr.setsize(imgThumbSrc.byteCount()) previewBaSrc = QByteArray(ptr.asstring()) lblPreviewSrc = QLabel() lblPreviewSrc.setPixmap(QPixmap.fromImage(imgThumbSrc)) lblPreviewSrc.setFixedHeight(imgThumbSrc.height() + 4) lblPreviewSrc.setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewContainer.addWidget(lblPreviewSrc) - lblPreviewLblSrc = QLabel(i18n("Original")) + lblPreviewLblSrc = QLabel(i18nc("the original layer", "Original")) lblPreviewLblSrc.setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewLblContainer.addWidget(lblPreviewLblSrc) vbxPreviewLblContainer.addWidget(QLabel(" ")) vbxPreviewContainer.addWidget(QLabel(">")) lblPreview[3].setPixmap(QPixmap.fromImage(imgThumbSrc)) lblPreview[3].setFixedHeight(imgThumbSrc.height() + 4) lblPreview[3].setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewContainer.addWidget(lblPreview[3]) lblPreviewLbl[3] = QLabel(i18n("Cyan")) lblPreviewLbl[3].setIndent(10) lblPreviewLbl[3].setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewLblContainer.addWidget(lblPreviewLbl[3]) lblPreview[2] = QLabel() lblPreview[2].setPixmap(QPixmap.fromImage(imgThumbSrc)) lblPreview[2].setFixedHeight(imgThumbSrc.height() + 4) lblPreview[2].setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewContainer.addWidget(lblPreview[2]) lblPreviewLbl[2] = QLabel(i18n("Magenta")) lblPreviewLbl[2].setIndent(10) lblPreviewLbl[2].setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewLblContainer.addWidget(lblPreviewLbl[2]) lblPreview[1] = QLabel() lblPreview[1].setPixmap(QPixmap.fromImage(imgThumbSrc)) lblPreview[1].setFixedHeight(imgThumbSrc.height() + 4) lblPreview[1].setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewContainer.addWidget(lblPreview[1]) lblPreviewLbl[1] = QLabel(i18n("Yellow")) lblPreviewLbl[1].setIndent(10) lblPreviewLbl[1].setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewLblContainer.addWidget(lblPreviewLbl[1]) lblPreview[0] = QLabel() lblPreview[0].setPixmap(QPixmap.fromImage(imgThumbSrc)) lblPreview[0].setFixedHeight(imgThumbSrc.height() + 4) lblPreview[0].setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewContainer.addWidget(lblPreview[0]) lblPreviewLbl[0] = QLabel(i18n("Black")) lblPreviewLbl[0].setIndent(10) lblPreviewLbl[0].setFixedWidth(imgThumbSrc.width() + 4) vbxPreviewLblContainer.addWidget(lblPreviewLbl[0]) vbxPreviewLblContainer.addStretch() vbxPreviewContainer.addStretch() buildPreview() returned = dlgMain.exec_() return returned def progressNext(self, pProgress): """Update progress bar""" if pProgress is not None: stepCurrent=pProgress.value()+1 pProgress.setValue(stepCurrent) QApplication.instance().processEvents() def run(self): """Run process for current layer""" pdlgProgress = QProgressDialog(self.__outputOptions['outputMode'], None, 0, 100, Application.activeWindow().qwindow()) pdlgProgress.setWindowTitle(PLUGIN_DIALOG_TITLE) pdlgProgress.setMinimumSize(640, 200) pdlgProgress.setModal(True) pdlgProgress.show() self.process(self.__sourceDocument, self.__sourceLayer, pdlgProgress) pdlgProgress.close() def process(self, pDocument, pOriginalLayer, pProgress): """Process given layer with current options""" self.layerNum = 0 document = pDocument originalLayer = pOriginalLayer parentGroupLayer = None currentProcessedLayer = None originalLayerIsVisible = originalLayer.visible() def getLayerByName(parent, value): """search and return a layer by name, within given parent group""" if parent == None: return document.nodeByName(value) for layer in parent.childNodes(): if layer.name() == value: return layer return None def duplicateLayer(currentProcessedLayer, value): """Duplicate layer from given name New layer become active layer """ newLayer = None srcLayer = None srcName = re.match("^@(.*)", value) if not srcName is None: # reference to a specific layer if srcName[1] == 'original': # original layer currently processed srcLayer = originalLayer else: # a color layer previously built (and finished) srcLayer = getLayerByName(parentGroupLayer, parseLayerName(self.__outputOptions['layerColorName'], srcName[1])) else: # a layer with a fixed name srcLayer = document.nodeByName(parseLayerName(value, '')) if not srcLayer is None: newLayer = srcLayer.duplicate() self.layerNum+=1 newLayer.setName("c2l-w{0}".format(self.layerNum)) parentGroupLayer.addChildNode(newLayer, currentProcessedLayer) return newLayer else: return None def newLayer(currentProcessedLayer, value): """Create a new layer of given type New layer become active layer """ newLayer = None if value is None or not value['type'] in ['filllayer']: # given type for new layer is not valid # currently only one layer type is implemented return None if value['type'] == 'filllayer': infoObject = InfoObject(); infoObject.setProperty("color", value['color']) selection = Selection(); selection.select(0, 0, document.width(), document.height(), 255) newLayer = document.createFillLayer(value['color'].name(), "color", infoObject, selection) if newLayer: self.layerNum+=1 newLayer.setName("c2l-w{0}".format(self.layerNum)) parentGroupLayer.addChildNode(newLayer, currentProcessedLayer) # Need to force generator otherwise, information provided when creating layer seems to not be taken in # account newLayer.setGenerator("color", infoObject) return newLayer else: return None def mergeDown(currentProcessedLayer, value): """Merge current layer with layer below""" if currentProcessedLayer is None: return None newLayer = currentProcessedLayer.mergeDown() # note: # when layer is merged down: # - a new layer seems to be created (reference to 'down' layer does not match anymore layer in group) # - retrieved 'newLayer' reference does not match to new layer resulting from merge # - activeNode() in document doesn't match to new layer resulting from merge # maybe it's norpmal, maybe not... # but the only solution to be able to work on merged layer (with current script) is to consider that from # parent node, last child match to last added layer and then, to our merged layer currentProcessedLayer = parentGroupLayer.childNodes()[-1] # for an unknown reason, merged layer bounds are not corrects... :'-( currentProcessedLayer.cropNode(0, 0, document.width(), document.height()) return currentProcessedLayer def applyBlendingMode(currentProcessedLayer, value): """Set blending mode for current layer""" if currentProcessedLayer is None or value is None or value == '': return False currentProcessedLayer.setBlendingMode(value) return True def applyFilter(currentProcessedLayer, value): """Apply filter to layer""" if currentProcessedLayer is None or value is None or value == '': return None filterName = re.match("name=([^;]+)", value) if filterName is None: return None filter = Application.filter(filterName.group(1)) filterConfiguration = filter.configuration() for parameter in value.split(';'): parameterName = re.match("^([^=]+)=(.*)", parameter) if not parameterName is None and parameterName != 'name': filterConfiguration.setProperty(parameterName.group(1), parameterName.group(2)) filter.setConfiguration(filterConfiguration) filter.apply(currentProcessedLayer, 0, 0, document.width(), document.height()) return currentProcessedLayer def parseLayerName(value, color): """Parse layer name""" returned = value returned = returned.replace("{source:name}", originalLayer.name()) returned = returned.replace("{mode}", OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['groupLayerName']) returned = returned.replace("{color:short}", color) if color == "C": returned = returned.replace("{color:long}", i18n("Cyan")) elif color == "M": returned = returned.replace("{color:long}", i18n("Magenta")) elif color == "Y": returned = returned.replace("{color:long}", i18n("Yellow")) elif color == "K": returned = returned.replace("{color:long}", i18n("Black")) elif color == "R": returned = returned.replace("{color:long}", i18n("Red")) elif color == "G": returned = returned.replace("{color:long}", i18n("Green")) elif color == "B": returned = returned.replace("{color:long}", i18n("Blue")) else: returned = returned.replace("{color:long}", "") return returned if document is None or originalLayer is None: # should not occurs, but... return None if not pProgress is None: stepTotal = 4 for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']: stepTotal+=len(layer['process']) pProgress.setRange(0, stepTotal) if originalLayerIsVisible == False: originalLayer.setVisible(True) # ---------------------------------------------------------------------- # Create new group layer parentGroupLayer = document.createGroupLayer(parseLayerName(self.__outputOptions['layerGroupName'], '')) self.progressNext(pProgress) currentProcessedLayer = None for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']: for process in layer['process']: if process['action'] == 'duplicate': currentProcessedLayer = duplicateLayer(currentProcessedLayer, process['value']) elif process['action'] == 'new': currentProcessedLayer = newLayer(currentProcessedLayer, process['value']) elif process['action'] == 'merge down': currentProcessedLayer = mergeDown(currentProcessedLayer, process['value']) pass elif process['action'] == 'blending mode': applyBlendingMode(currentProcessedLayer, process['value']) elif process['action'] == 'filter': applyFilter(currentProcessedLayer, process['value']) self.progressNext(pProgress) if not currentProcessedLayer is None: # rename currentProcessedLayer currentProcessedLayer.setName(parseLayerName(self.__outputOptions['layerColorName'], layer['color'])) document.rootNode().addChildNode(parentGroupLayer, originalLayer) self.progressNext(pProgress) if self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPVISIBLE: originalLayer.setVisible(True) elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPHIDDEN: originalLayer.setVisible(False) elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_REMOVE: originalLayer.remove() else: # ORIGINAL_LAYER_KEEPUNCHANGED originalLayer.setVisible(originalLayerIsVisible) self.progressNext(pProgress) document.refreshProjection() self.progressNext(pProgress) document.setActiveNode(parentGroupLayer) return parentGroupLayer #ChannelsToLayers(Krita.instance()).process(Application.activeDocument(), Application.activeDocument().activeNode(), None) #ChannelsToLayers(Krita.instance()).action_triggered() diff --git a/plugins/python/channels2layers/kritapykrita_channels2layers.desktop b/plugins/python/channels2layers/kritapykrita_channels2layers.desktop index 2d4ceca40b..6ce6331805 100644 --- a/plugins/python/channels2layers/kritapykrita_channels2layers.desktop +++ b/plugins/python/channels2layers/kritapykrita_channels2layers.desktop @@ -1,26 +1,30 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=channels2layers X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Channels to layers Name[ca]=Canals a capes +Name[en_GB]=Channels to layers Name[es]=Canales a capas Name[et]=Kanalid kihtideks Name[nl]=Kanalen naar lagen Name[nn]=Kanalar til lag Name[pt]=Canais para camadas Name[pt_BR]=Canais para camadas +Name[sv]=Kanaler till lager Name[uk]=Канали у шари Name[x-test]=xxChannels to layersxx Comment=Extract channels as color layers Comment[ca]=Extreu canals com a capes de color +Comment[en_GB]=Extract channels as colour layers Comment[es]=Extraer canales como capas de color Comment[et]=Kanalite ekstraktimine värvikihtidena Comment[nl]=Kanalen als kleurlagen extraheren Comment[nn]=Hent ut kanalar som fargelag Comment[pt]=Extrair os canais como camadas de cores Comment[pt_BR]=Extraia canais como camadas de cores +Comment[sv]=Extrahera kanaler som färglager Comment[uk]=Видобування каналів як колірних шарів Comment[x-test]=xxExtract channels as color layersxx diff --git a/plugins/tools/basictools/KisToolPath.action b/plugins/tools/basictools/KisToolPath.action index 3044779762..05dfb42334 100644 --- a/plugins/tools/basictools/KisToolPath.action +++ b/plugins/tools/basictools/KisToolPath.action @@ -1,6 +1,16 @@ - - Path Tool + + Tool Shortcuts + + + Bezier Curve Tool + + Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. + Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. + + false + + diff --git a/plugins/tools/basictools/KisToolPencil.action b/plugins/tools/basictools/KisToolPencil.action index c7f2622025..0ce9733607 100644 --- a/plugins/tools/basictools/KisToolPencil.action +++ b/plugins/tools/basictools/KisToolPencil.action @@ -1,6 +1,16 @@ - - Pencil Tool + + Tool Shortcuts + + + Freehand Path Tool + + Freehand Path Tool + Freehand Path Tool + + false + + diff --git a/plugins/tools/selectiontools/KisToolSelectContiguous.action b/plugins/tools/selectiontools/KisToolSelectContiguous.action index 34752ec368..9b68e10c47 100644 --- a/plugins/tools/selectiontools/KisToolSelectContiguous.action +++ b/plugins/tools/selectiontools/KisToolSelectContiguous.action @@ -1,6 +1,16 @@ - - Contiguous Selection Tool + + Tool Shortcuts + + + Contiguous Selection Tool + + Contiguous Selection Tool + Contiguous Selection Tool + + false + + diff --git a/plugins/tools/selectiontools/KisToolSelectElliptical.action b/plugins/tools/selectiontools/KisToolSelectElliptical.action index 10ff8c9114..a9b2608c0d 100644 --- a/plugins/tools/selectiontools/KisToolSelectElliptical.action +++ b/plugins/tools/selectiontools/KisToolSelectElliptical.action @@ -1,6 +1,16 @@ - - Elliptical Selection Tool + + Tool Shortcuts + + + Elliptical Selection Tool + + Elliptical Selection Tool + Elliptical Selection Tool + J + false + + diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.action b/plugins/tools/selectiontools/KisToolSelectMagnetic.action index fe0fbb9f7c..1e99ccfd72 100644 --- a/plugins/tools/selectiontools/KisToolSelectMagnetic.action +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.action @@ -1,6 +1,16 @@ - - Magnetic Selection Tool + + Tool Shortcuts + + + Magnetic Selection Tool + + Magnetic Curve Selection Tool + Magnetic Curve Selection Tool + + false + + diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc index 0df388f98a..d3157386e2 100644 --- a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc @@ -1,747 +1,752 @@ /* * Copyright (c) 2019 Kuntal Majumder * * 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 "KisToolSelectMagnetic.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_painter.h" #include #include "canvas/kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_algebra_2d.h" #include "KisHandlePainterHelper.h" #include #define FEEDBACK_LINE_WIDTH 2 KisToolSelectMagnetic::KisToolSelectMagnetic(KoCanvasBase *canvas) : KisToolSelect(canvas, KisCursor::load("tool_magnetic_selection_cursor.png", 5, 5), i18n("Magnetic Selection")), m_continuedMode(false), m_complete(false), m_selected(false), m_finished(false), m_worker(image()->projection()), m_threshold(70), m_searchRadius(30), m_anchorGap(30), m_filterRadius(3.0), m_mouseHoverCompressor(100, KisSignalCompressor::FIRST_ACTIVE) { } void KisToolSelectMagnetic::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control) { m_continuedMode = true; } KisToolSelect::keyPressEvent(event); } /* * Calculates the checkpoints responsible to determining the last point from where * the edge is calculated. * Takes 3 point, min, median and max, searches for an edge point from median to max, if fails, * searches for the same from median to min, if fails, median becomes that edge point. */ void KisToolSelectMagnetic::calculateCheckPoints(vQPointF points) { qreal totalDistance = 0.0; int checkPoint = 0; int finalPoint = 2; int midPoint = 1; int minPoint = 0; qreal maxFactor = 2; for (; finalPoint < points.count(); finalPoint++) { totalDistance += kisDistance(points[finalPoint], points[finalPoint - 1]); if (totalDistance <= m_anchorGap / 3) { minPoint = finalPoint; } if (totalDistance <= m_anchorGap) { midPoint = finalPoint; } if (totalDistance > maxFactor * m_anchorGap) { break; } } if (totalDistance > maxFactor * m_anchorGap) { bool foundSomething = false; for (int i = midPoint; i < finalPoint; i++) { if (m_worker.intensity(points.at(i).toPoint()) >= m_threshold) { m_lastAnchor = points.at(i).toPoint(); m_anchorPoints.push_back(m_lastAnchor); vQPointF temp; for (int j = 0; j <= i; j++) { temp.push_back(points[j]); } m_pointCollection.push_back(temp); foundSomething = true; checkPoint = i; break; } } if (!foundSomething) { for (int i = midPoint - 1; i >= minPoint; i--) { if (m_worker.intensity(points.at(i).toPoint()) >= m_threshold) { m_lastAnchor = points.at(i).toPoint(); m_anchorPoints.push_back(m_lastAnchor); vQPointF temp; for (int j = midPoint - 1; j >= i; j--) { temp.push_front(points[j]); } m_pointCollection.push_back(temp); foundSomething = true; checkPoint = i; break; } } } if (!foundSomething) { m_lastAnchor = points[midPoint].toPoint(); m_anchorPoints.push_back(m_lastAnchor); vQPointF temp; for (int j = 0; j <= midPoint; j++) { temp.push_back(points[j]); } m_pointCollection.push_back(temp); checkPoint = midPoint; foundSomething = true; } } totalDistance = 0.0; reEvaluatePoints(); for (; finalPoint < points.count(); finalPoint++) { totalDistance += kisDistance(points[finalPoint], points[checkPoint]); if (totalDistance > maxFactor * m_anchorGap) { points.remove(0, checkPoint + 1); calculateCheckPoints(points); break; } } } // KisToolSelectMagnetic::calculateCheckPoints void KisToolSelectMagnetic::keyReleaseEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control || !(event->modifiers() & Qt::ControlModifier)) { m_continuedMode = false; if (mode() != PAINT_MODE && !m_points.isEmpty()) { finishSelectionAction(); } } KisToolSelect::keyReleaseEvent(event); } vQPointF KisToolSelectMagnetic::computeEdgeWrapper(QPoint a, QPoint b) { return m_worker.computeEdge(m_searchRadius, a, b, m_filterRadius); } // the cursor is still tracked even when no mousebutton is pressed void KisToolSelectMagnetic::mouseMoveEvent(KoPointerEvent *event) { m_lastCursorPos = convertToPixelCoord(event); KisToolSelect::mouseMoveEvent(event); updatePaintPath(); } // KisToolSelectMagnetic::mouseMoveEvent // press primary mouse button void KisToolSelectMagnetic::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); QPointF temp(convertToPixelCoord(event)); if (!image()->bounds().contains(temp.toPoint())) { return; } m_cursorOnPress = temp; checkIfAnchorIsSelected(temp); if (m_complete || m_selected) { return; } if (m_anchorPoints.count() != 0) { vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint()); m_points.append(edge); m_pointCollection.push_back(edge); } else { updateInitialAnchorBounds(temp.toPoint()); } m_lastAnchor = temp.toPoint(); m_anchorPoints.push_back(m_lastAnchor); m_lastCursorPos = temp; reEvaluatePoints(); updateCanvasPixelRect(image()->bounds()); } // KisToolSelectMagnetic::beginPrimaryAction void KisToolSelectMagnetic::checkIfAnchorIsSelected(QPointF temp) { Q_FOREACH (const QPoint pt, m_anchorPoints) { qreal zoomLevel = canvas()->viewConverter()->zoom(); int sides = (int) std::ceil(10.0 / zoomLevel); QRect r = QRect(QPoint(0, 0), QSize(sides, sides)); r.moveCenter(pt); if (r.contains(temp.toPoint())) { m_selected = true; m_selectedAnchor = m_anchorPoints.lastIndexOf(pt); return; } } } /* ~~TODO ALERT~~ The double click adds a bit more functionality to the tool but also the reason of multiple problems, so disabling it for now, if someone can find some alternate ways for mimicking what the double clicks intended to do, please drop a patch void KisToolSelectMagnetic::beginPrimaryDoubleClickAction(KoPointerEvent *event) { QPointF temp = convertToPixelCoord(event); if (!image()->bounds().contains(temp.toPoint())) { return; } checkIfAnchorIsSelected(temp); if (m_selected) { deleteSelectedAnchor(); return; } if (m_complete) { int pointA = 0, pointB = 1; double dist = std::numeric_limits::max(); int total = m_anchorPoints.count(); for (int i = 0; i < total; i++) { double distToCompare = kisDistance(m_anchorPoints[i], temp) + kisDistance(temp, m_anchorPoints[(i + 1) % total]); if (distToCompare < dist) { pointA = i; pointB = (i + 1) % total; dist = distToCompare; } } vQPointF path1 = computeEdgeWrapper(m_anchorPoints[pointA], temp.toPoint()); vQPointF path2 = computeEdgeWrapper(temp.toPoint(), m_anchorPoints[pointB]); m_pointCollection[pointA] = path1; m_pointCollection.insert(pointB, path2); m_anchorPoints.insert(pointB, temp.toPoint()); reEvaluatePoints(); } } // KisToolSelectMagnetic::beginPrimaryDoubleClickAction */ // drag while primary mouse button is pressed void KisToolSelectMagnetic::continuePrimaryAction(KoPointerEvent *event) { if (m_selected) { m_anchorPoints[m_selectedAnchor] = convertToPixelCoord(event).toPoint(); } else if (!m_complete) { m_lastCursorPos = convertToPixelCoord(event); if(kisDistance(m_lastCursorPos, m_cursorOnPress) >= m_anchorGap) m_mouseHoverCompressor.start(); } KisToolSelectBase::continuePrimaryAction(event); } void KisToolSelectMagnetic::slotCalculateEdge() { QPoint current = m_lastCursorPos.toPoint(); if (!image()->bounds().contains(current)) return; if(kisDistance(m_lastAnchor, current) < m_anchorGap) return; vQPointF pointSet = computeEdgeWrapper(m_lastAnchor, current); calculateCheckPoints(pointSet); } // release primary mouse button void KisToolSelectMagnetic::endPrimaryAction(KoPointerEvent *event) { if (m_selected && convertToPixelCoord(event) != m_cursorOnPress) { if (!image()->bounds().contains(m_anchorPoints[m_selectedAnchor])) { deleteSelectedAnchor(); } else { updateSelectedAnchor(); } } else if (m_selected) { QPointF temp(convertToPixelCoord(event)); if (!image()->bounds().contains(temp.toPoint())) { return; } if (m_snapBound.contains(temp) && m_anchorPoints.count() > 1) { if(m_complete){ finishSelectionAction(); return; } vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint()); m_points.append(edge); m_pointCollection.push_back(edge); m_complete = true; } } if (m_mouseHoverCompressor.isActive()) { m_mouseHoverCompressor.stop(); slotCalculateEdge(); } m_selected = false; KisToolSelectBase::endPrimaryAction(event); } // KisToolSelectMagnetic::endPrimaryAction void KisToolSelectMagnetic::deleteSelectedAnchor() { if (m_anchorPoints.isEmpty()) return; // if it is the initial anchor if (m_selectedAnchor == 0) { m_anchorPoints.pop_front(); if (m_anchorPoints.isEmpty()) { // it was the only point lol resetVariables(); reEvaluatePoints(); return; } m_pointCollection.pop_front(); if (m_complete) { m_pointCollection[0] = computeEdgeWrapper(m_anchorPoints.first(), m_anchorPoints.last()); } reEvaluatePoints(); return; } // if it is the last anchor if (m_selectedAnchor == m_anchorPoints.count() - 1) { m_anchorPoints.pop_back(); m_pointCollection.pop_back(); if (m_complete) { m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints.last(), m_anchorPoints.first()); } reEvaluatePoints(); return; } // it is in the middle m_anchorPoints.remove(m_selectedAnchor); m_pointCollection.remove(m_selectedAnchor); m_pointCollection[m_selectedAnchor - 1] = computeEdgeWrapper(m_anchorPoints[m_selectedAnchor - 1], m_anchorPoints[m_selectedAnchor]); reEvaluatePoints(); } // KisToolSelectMagnetic::deleteSelectedAnchor void KisToolSelectMagnetic::updateSelectedAnchor() { + //the only anchor + if (m_anchorPoints.count() == 1) { + return; + } + // initial if (m_selectedAnchor == 0 && m_anchorPoints.count() > 1) { m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints[0], m_anchorPoints[1]); if (m_complete) { m_pointCollection[m_anchorPoints.count() - 1] = computeEdgeWrapper(m_anchorPoints.last(), m_anchorPoints.first()); } reEvaluatePoints(); return; } // last if (m_selectedAnchor == m_anchorPoints.count() - 1) { m_pointCollection[m_selectedAnchor - 1] = computeEdgeWrapper(m_anchorPoints[m_selectedAnchor - 1], m_anchorPoints.last()); if (m_complete) { m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints.last(), m_anchorPoints.first()); } reEvaluatePoints(); return; } // middle m_pointCollection[m_selectedAnchor - 1] = computeEdgeWrapper(m_anchorPoints[m_selectedAnchor - 1], m_anchorPoints[m_selectedAnchor]); m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints[m_selectedAnchor], m_anchorPoints[m_selectedAnchor + 1]); reEvaluatePoints(); } int KisToolSelectMagnetic::updateInitialAnchorBounds(QPoint pt) { qreal zoomLevel = canvas()->viewConverter()->zoom(); int sides = (int) std::ceil(10.0 / zoomLevel); m_snapBound = QRectF(QPoint(0, 0), QSize(sides, sides)); m_snapBound.moveCenter(pt); return sides; } void KisToolSelectMagnetic::reEvaluatePoints() { m_points.clear(); Q_FOREACH (const vQPointF vec, m_pointCollection) { m_points.append(vec); } updatePaintPath(); } void KisToolSelectMagnetic::finishSelectionAction() { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); setMode(KisTool::HOVER_MODE); m_complete = false; m_finished = true; // just for testing out // m_worker.saveTheImage(m_points); QRectF boundingViewRect = pixelToView(KisAlgebra2D::accumulateBounds(m_points)); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Magnetic Selection")); if (m_points.count() > 2 && !helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) { QApplication::setOverrideCursor(KisCursor::waitCursor()); const SelectionMode mode = helper.tryOverrideSelectionMode(kisCanvas->viewManager()->selection(), selectionMode(), selectionAction()); if (mode == PIXEL_SELECTION) { KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setAntiAliasPolygonFill(antiAliasSelection()); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setStrokeStyle(KisPainter::StrokeStyleNone); painter.paintPolygon(m_points); QPainterPath cache; cache.addPolygon(m_points); cache.closeSubpath(); tmpSel->setOutlineCache(cache); helper.selectPixelSelection(tmpSel, selectionAction()); } else { KoPathShape *path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_points[0])); for (int i = 1; i < m_points.count(); i++) path->lineTo(resolutionMatrix.map(m_points[i])); path->close(); path->normalize(); helper.addSelectionShape(path, selectionAction()); } QApplication::restoreOverrideCursor(); } resetVariables(); } // KisToolSelectMagnetic::finishSelectionAction void KisToolSelectMagnetic::resetVariables() { m_points.clear(); m_anchorPoints.clear(); m_pointCollection.clear(); m_paintPath = QPainterPath(); } void KisToolSelectMagnetic::updatePaintPath() { m_paintPath = QPainterPath(); if (m_points.size() > 0) { m_paintPath.moveTo(pixelToView(m_points[0])); } for (int i = 1; i < m_points.count(); i++) { m_paintPath.lineTo(pixelToView(m_points[i])); } updateFeedback(); if (m_continuedMode && mode() != PAINT_MODE) { updateContinuedMode(); } updateCanvasPixelRect(image()->bounds()); } void KisToolSelectMagnetic::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter) updatePaintPath(); if ((mode() == KisTool::PAINT_MODE || m_continuedMode) && !m_anchorPoints.isEmpty()) { QPainterPath outline = m_paintPath; if (m_continuedMode && mode() != KisTool::PAINT_MODE) { outline.lineTo(pixelToView(m_lastCursorPos)); } paintToolOutline(&gc, outline); drawAnchors(gc); } } void KisToolSelectMagnetic::drawAnchors(QPainter &gc) { int sides = updateInitialAnchorBounds(m_anchorPoints.first()); Q_FOREACH (const QPoint pt, m_anchorPoints) { KisHandlePainterHelper helper(&gc, handleRadius()); QRect r(QPoint(0, 0), QSize(sides, sides)); r.moveCenter(pt); if (r.contains(m_lastCursorPos.toPoint())) { helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles()); } else { helper.setHandleStyle(KisHandleStyle::primarySelection()); } helper.drawHandleRect(pixelToView(pt), 4, QPoint(0, 0)); } } void KisToolSelectMagnetic::updateFeedback() { if (m_points.count() > 1) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectMagnetic::updateContinuedMode() { if (!m_points.isEmpty()) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectMagnetic::activate(KoToolBase::ToolActivation activation, const QSet &shapes) { m_worker = KisMagneticWorker(image()->projection()); m_configGroup = KSharedConfig::openConfig()->group(toolId()); connect(action("undo_polygon_selection"), SIGNAL(triggered()), SLOT(undoPoints()), Qt::UniqueConnection); connect(&m_mouseHoverCompressor, SIGNAL(timeout()), this, SLOT(slotCalculateEdge())); KisToolSelect::activate(activation, shapes); } void KisToolSelectMagnetic::deactivate() { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); resetVariables(); m_continuedMode = false; disconnect(action("undo_polygon_selection"), nullptr, this, nullptr); KisTool::deactivate(); } void KisToolSelectMagnetic::undoPoints() { if (m_complete) return; if(m_anchorPoints.count() <= 1){ resetVariables(); return; } m_anchorPoints.pop_back(); m_pointCollection.pop_back(); reEvaluatePoints(); } void KisToolSelectMagnetic::requestStrokeEnd() { if (m_finished || m_anchorPoints.count() < 2) return; finishSelectionAction(); m_finished = false; } void KisToolSelectMagnetic::requestStrokeCancellation() { m_complete = false; m_finished = false; resetVariables(); } QWidget * KisToolSelectMagnetic::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); QHBoxLayout *f1 = new QHBoxLayout(); QLabel *filterRadiusLabel = new QLabel(i18n("Filter Radius: "), selectionWidget); f1->addWidget(filterRadiusLabel); KisDoubleSliderSpinBox *filterRadiusInput = new KisDoubleSliderSpinBox(selectionWidget); filterRadiusInput->setObjectName("radius"); filterRadiusInput->setRange(2.5, 100.0, 2); filterRadiusInput->setSingleStep(0.5); filterRadiusInput->setToolTip("Radius of the filter for the detecting edges, might take some time to calculate"); f1->addWidget(filterRadiusInput); connect(filterRadiusInput, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetFilterRadius(qreal))); QHBoxLayout *f2 = new QHBoxLayout(); QLabel *thresholdLabel = new QLabel(i18n("Threshold: "), selectionWidget); f2->addWidget(thresholdLabel); KisSliderSpinBox *thresholdInput = new KisSliderSpinBox(selectionWidget); thresholdInput->setObjectName("threshold"); thresholdInput->setRange(1, 255); thresholdInput->setSingleStep(10); thresholdInput->setToolTip("Threshold for determining the minimum intensity of the edges"); f2->addWidget(thresholdInput); connect(thresholdInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetThreshold(int))); QHBoxLayout *f3 = new QHBoxLayout(); QLabel *searchRadiusLabel = new QLabel(i18n("Search Radius: "), selectionWidget); f3->addWidget(searchRadiusLabel); KisSliderSpinBox *searchRadiusInput = new KisSliderSpinBox(selectionWidget); searchRadiusInput->setObjectName("frequency"); searchRadiusInput->setRange(20, 200); searchRadiusInput->setSingleStep(10); searchRadiusInput->setToolTip("Extra area to be searched"); searchRadiusInput->setSuffix(" px"); f3->addWidget(searchRadiusInput); connect(searchRadiusInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetSearchRadius(int))); QHBoxLayout *f4 = new QHBoxLayout(); QLabel *anchorGapLabel = new QLabel(i18n("Anchor Gap: "), selectionWidget); f4->addWidget(anchorGapLabel); KisSliderSpinBox *anchorGapInput = new KisSliderSpinBox(selectionWidget); anchorGapInput->setObjectName("anchorgap"); anchorGapInput->setRange(20, 200); anchorGapInput->setSingleStep(10); anchorGapInput->setToolTip("Gap between 2 anchors in interative mode"); anchorGapInput->setSuffix(" px"); f4->addWidget(anchorGapInput); connect(anchorGapInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetAnchorGap(int))); QVBoxLayout *l = dynamic_cast(selectionWidget->layout()); l->insertLayout(1, f1); l->insertLayout(2, f2); l->insertLayout(3, f3); l->insertLayout(5, f4); filterRadiusInput->setValue(m_configGroup.readEntry("filterradius", 3.0)); thresholdInput->setValue(m_configGroup.readEntry("threshold", 100)); searchRadiusInput->setValue(m_configGroup.readEntry("searchradius", 30)); anchorGapInput->setValue(m_configGroup.readEntry("anchorgap", 20)); return selectionWidget; } // KisToolSelectMagnetic::createOptionWidget void KisToolSelectMagnetic::slotSetFilterRadius(qreal r) { m_filterRadius = r; m_configGroup.writeEntry("filterradius", r); } void KisToolSelectMagnetic::slotSetThreshold(int t) { m_threshold = t; m_configGroup.writeEntry("threshold", t); } void KisToolSelectMagnetic::slotSetSearchRadius(int r) { m_searchRadius = r; m_configGroup.writeEntry("searchradius", r); } void KisToolSelectMagnetic::slotSetAnchorGap(int g) { m_anchorGap = g; m_configGroup.writeEntry("anchorgap", g); } void KisToolSelectMagnetic::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_magnetic_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_magnetic_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/plugins/tools/selectiontools/KisToolSelectOutline.action b/plugins/tools/selectiontools/KisToolSelectOutline.action index 714dcda311..878c6584af 100644 --- a/plugins/tools/selectiontools/KisToolSelectOutline.action +++ b/plugins/tools/selectiontools/KisToolSelectOutline.action @@ -1,6 +1,16 @@ - - Outline Selection Tool + +Tool Shortcuts + + + Outline Selection Tool + + Outline Selection Tool + Outline Selection Tool + + false + + diff --git a/plugins/tools/selectiontools/KisToolSelectPath.action b/plugins/tools/selectiontools/KisToolSelectPath.action index fe11abdd03..e2fa7703e1 100644 --- a/plugins/tools/selectiontools/KisToolSelectPath.action +++ b/plugins/tools/selectiontools/KisToolSelectPath.action @@ -1,6 +1,16 @@ - - Path Selection Tool + + Tool Shortcuts + + + Bezier Curve Selection Tool + + Bezier Curve Selection Tool + Bezier Curve Selection Tool + + false + + diff --git a/plugins/tools/selectiontools/KisToolSelectPolygonal.action b/plugins/tools/selectiontools/KisToolSelectPolygonal.action index 7b2bbc0e37..cb9545d610 100644 --- a/plugins/tools/selectiontools/KisToolSelectPolygonal.action +++ b/plugins/tools/selectiontools/KisToolSelectPolygonal.action @@ -1,6 +1,14 @@ - - Polygonal Selection Tool + +Tool Shortcuts + + + Polygonal Selection Tool + Polygonal Selection Tool + + false + + diff --git a/plugins/tools/selectiontools/KisToolSelectRectangular.action b/plugins/tools/selectiontools/KisToolSelectRectangular.action index add07b5dc7..104fd298c8 100644 --- a/plugins/tools/selectiontools/KisToolSelectRectangular.action +++ b/plugins/tools/selectiontools/KisToolSelectRectangular.action @@ -1,6 +1,16 @@ - - Rectangular Selection Tool + + Tool Shortcuts + + + Rectangular Selection Tool + + Rectangular Selection Tool + Rectangular Selection Tool + Ctrl+R + false + + diff --git a/plugins/tools/selectiontools/KisToolSelectSimilar.action b/plugins/tools/selectiontools/KisToolSelectSimilar.action index 000cd4cf6e..c57145eb5a 100644 --- a/plugins/tools/selectiontools/KisToolSelectSimilar.action +++ b/plugins/tools/selectiontools/KisToolSelectSimilar.action @@ -1,6 +1,16 @@ - - Similar Selection Tool + + Tool Shortcuts + + + Similar Color Selection Tool + + Similar Color Selection Tool + Similar Color Selection Tool + + false + + diff --git a/plugins/tools/svgtexttool/SvgTextTool.action b/plugins/tools/svgtexttool/SvgTextTool.action index 498e83cf6a..d15dfddfcb 100644 --- a/plugins/tools/svgtexttool/SvgTextTool.action +++ b/plugins/tools/svgtexttool/SvgTextTool.action @@ -1,255 +1,256 @@ SVG Text Tool Text Tool Text Tool Text Tool false Text Color Text Color... format-text-color false Text Color Background Background Color... format-fill-color false Background Font Size Font Size false Font Size Font Ctrl+Alt+F Change character size, font, boldface, italics etc. Change the attributes of the currently selected characters. false Font... Special Character Alt+Shift+C Insert one or more symbols or characters not found on the keyboard character-set Insert one or more symbols or characters not found on the keyboard. false Special Character... Align Right Ctrl+Alt+R Align Right format-justify-right false Align Right Align Left Align Left format-justify-left false Align Left Align Block Ctrl+Alt+R Align Block format-justify-fill false Align Block Align Center Ctrl+Alt+C Align Center format-justify-center false Align Center Decrease Font Size Ctrl+< Decrease Font Size false Decrease Font Size Increase Font Size Ctrl+> Increase Font Size false Increase Font Size Subscript Ctrl+Shift+B Subscript format-text-subscript false Subscript Superscript Ctrl+Shift+P Superscript format-text-superscript false Superscript Underline Ctrl+U Underline format-text-underline false Underline Strikethrough Strikethrough format-text-strikethrough false Strikethrough Bold Ctrl+B Bold format-text-bold true Bold Italic Ctrl+I Italic format-text-italic true Italic Normal Ctrl+N Normal format-text-normal false Normal Demi Demi format-text-demi false Demi Black Black format-text-black false Black Light Ctrl+L Light format-text-light false Light Line Height Ctrl+H Line Height in em false Line Height Settings Text Editor Settings false Settings... - +
Colorpicker Pick a color from the screen false Color Picker -
+ + diff --git a/plugins/tools/tool_crop/KisToolCrop.action b/plugins/tools/tool_crop/KisToolCrop.action index 4a43016b35..8d10404ddc 100644 --- a/plugins/tools/tool_crop/KisToolCrop.action +++ b/plugins/tools/tool_crop/KisToolCrop.action @@ -1,6 +1,16 @@ - - Crop Tool + + Tool Shortcuts + + + Crop Tool + + Crop the image to an area + Crop the image to an area + C + false + + diff --git a/plugins/tools/tool_polygon/KisToolPolygon.action b/plugins/tools/tool_polygon/KisToolPolygon.action index 7789b27186..73844c344a 100644 --- a/plugins/tools/tool_polygon/KisToolPolygon.action +++ b/plugins/tools/tool_polygon/KisToolPolygon.action @@ -1,6 +1,16 @@ - - Polygon Tool + + Tool Shortcuts + + + Polygon Tool + + Polygon Tool. Shift-mouseclick ends the polygon. + Polygon Tool. Shift-mouseclick ends the polygon. + + false + + diff --git a/plugins/tools/tool_polyline/KisToolPolyline.action b/plugins/tools/tool_polyline/KisToolPolyline.action index 421c60dc3c..691b046907 100644 --- a/plugins/tools/tool_polyline/KisToolPolyline.action +++ b/plugins/tools/tool_polyline/KisToolPolyline.action @@ -1,6 +1,16 @@ - - Polyline Tool + + Tool Shortcuts + + + Polyline Tool + + Polyline Tool. Shift-mouseclick ends the polyline. + Polyline Tool. Shift-mouseclick ends the polyline. + + false + + diff --git a/plugins/tools/tool_transform2/KisToolTransform.action b/plugins/tools/tool_transform2/KisToolTransform.action index 16fa4de4c5..88dfdd506b 100644 --- a/plugins/tools/tool_transform2/KisToolTransform.action +++ b/plugins/tools/tool_transform2/KisToolTransform.action @@ -1,6 +1,16 @@ - - Transform Tool + + Tool Shortcuts + + + Transform Tool + + Transform a layer or a selection + Transform a layer or a selection + Ctrl+T + false + + diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp index bfbc5e2546..628debde9a 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp +++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp @@ -1,1288 +1,1288 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform_config_widget.h" #include #include "rotation_icons.h" #include "kis_canvas2.h" #include #include "kis_liquify_properties.h" #include "KisMainWindow.h" #include "KisViewManager.h" #include "kis_transform_utils.h" template inline T sign(T x) { return x > 0 ? 1 : x == (T)0 ? 0 : -1; } const int KisToolTransformConfigWidget::DEFAULT_POINTS_PER_LINE = 3; KisToolTransformConfigWidget::KisToolTransformConfigWidget(TransformTransactionProperties *transaction, KisCanvas2 *canvas, bool workRecursively, QWidget *parent) : QWidget(parent), m_transaction(transaction), m_notificationsBlocked(0), m_uiSlotsBlocked(0), m_configChanged(false) { setupUi(this); chkWorkRecursively->setIcon(KisIconUtils::loadIcon("krita_tool_transform_recursive")); flipXButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_x")); flipYButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_y")); rotateCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_cw")); rotateCCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_ccw")); chkWorkRecursively->setChecked(workRecursively); connect(chkWorkRecursively, SIGNAL(toggled(bool)), this, SIGNAL(sigRestartTransform())); // Granularity can only be specified in the power of 2's QStringList granularityValues{"4","8","16","32"}; changeGranularity->addItems(granularityValues); changeGranularity->setCurrentIndex(1); granularityPreview->addItems(granularityValues); granularityPreview->setCurrentIndex(2); connect(changeGranularity,SIGNAL(currentIndexChanged(QString)), this,SLOT(slotGranularityChanged(QString))); connect(granularityPreview, SIGNAL(currentIndexChanged(QString)), this,SLOT(slotPreviewGranularityChanged(QString))); // Init Filter combo cmbFilter->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); cmbFilter->setCurrent("Bicubic"); cmbFilter->setToolTip(i18nc("@info:tooltip", "

Select filtering mode:\n" "

    " "
  • Bilinear for areas with uniform color to avoid artifacts
  • " "
  • Bicubic for smoother results
  • " "
  • Lanczos3 for sharp results. May produce aerials.
  • " "

")); connect(cmbFilter, SIGNAL(activated(KoID)), this, SLOT(slotFilterChanged(KoID))); // Init Warp Type combo cmbWarpType->insertItem(KisWarpTransformWorker::AFFINE_TRANSFORM,i18n("Default (Affine)")); cmbWarpType->insertItem(KisWarpTransformWorker::RIGID_TRANSFORM,i18n("Strong (Rigid)")); cmbWarpType->insertItem(KisWarpTransformWorker::SIMILITUDE_TRANSFORM,i18n("Strongest (Similitude)")); cmbWarpType->setCurrentIndex(KisWarpTransformWorker::AFFINE_TRANSFORM); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWarpTypeChanged(int))); // Init Rotation Center buttons m_handleDir[0] = QPointF(1, 0); m_handleDir[1] = QPointF(1, -1); m_handleDir[2] = QPointF(0, -1); m_handleDir[3] = QPointF(-1, -1); m_handleDir[4] = QPointF(-1, 0); m_handleDir[5] = QPointF(-1, 1); m_handleDir[6] = QPointF(0, 1); m_handleDir[7] = QPointF(1, 1); m_handleDir[8] = QPointF(0, 0); // also add the center m_rotationCenterButtons = new QButtonGroup(0); // we set the ids to match m_handleDir m_rotationCenterButtons->addButton(middleRightButton, 0); m_rotationCenterButtons->addButton(topRightButton, 1); m_rotationCenterButtons->addButton(middleTopButton, 2); m_rotationCenterButtons->addButton(topLeftButton, 3); m_rotationCenterButtons->addButton(middleLeftButton, 4); m_rotationCenterButtons->addButton(bottomLeftButton, 5); m_rotationCenterButtons->addButton(middleBottomButton, 6); m_rotationCenterButtons->addButton(bottomRightButton, 7); m_rotationCenterButtons->addButton(centerButton, 8); QToolButton *nothingSelected = new QToolButton(0); nothingSelected->setCheckable(true); nothingSelected->setAutoExclusive(true); nothingSelected->hide(); // a convenient button for when no button is checked in the group m_rotationCenterButtons->addButton(nothingSelected, 9); // initialize values for free transform sliders - shearXBox->setSuffix(i18n(" px")); - shearYBox->setSuffix(i18n(" px")); - shearXBox->setRange(-5.0, 5.0, 2); - shearYBox->setRange(-5.0, 5.0, 2); - shearXBox->setSingleStep(0.01); - shearYBox->setSingleStep(0.01); + shearXBox->setSuffix(QChar(Qt::Key_Percent)); + shearYBox->setSuffix(QChar(Qt::Key_Percent)); + shearXBox->setRange(-500, 500, 2); + shearYBox->setRange(-500, 500, 2); + shearXBox->setSingleStep(1); + shearYBox->setSingleStep(1); shearXBox->setValue(0.0); shearYBox->setValue(0.0); translateXBox->setSuffix(i18n(" px")); translateYBox->setSuffix(i18n(" px")); translateXBox->setRange(-10000, 10000); translateYBox->setRange(-10000, 10000); scaleXBox->setRange(-10000, 10000); scaleYBox->setRange(-10000, 10000); scaleXBox->setValue(100.0); scaleYBox->setValue(100.0); m_scaleRatio = 1.0; aXBox->setSuffix(QChar(Qt::Key_degree)); aYBox->setSuffix(QChar(Qt::Key_degree)); aZBox->setSuffix(QChar(Qt::Key_degree)); aXBox->setRange(0.0, 360.0, 2); aYBox->setRange(0.0, 360.0, 2); aZBox->setRange(0.0, 360.0, 2); aXBox->setValue(0.0); aYBox->setValue(0.0); aZBox->setValue(0.0); aXBox->setSingleStep(1.0); aYBox->setSingleStep(1.0); aZBox->setSingleStep(1.0); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(slotRotationCenterChanged(int))); connect(btnTransformAroundPivotPoint, SIGNAL(clicked(bool)), this, SLOT(slotTransformAroundRotationCenter(bool))); // Init Free Transform Values connect(scaleXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleX(int))); connect(scaleYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleY(int))); connect(shearXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearX(qreal))); connect(shearYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearY(qreal))); connect(translateXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateX(int))); connect(translateYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateY(int))); connect(aXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAX(qreal))); connect(aYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAY(qreal))); connect(aZBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAZ(qreal))); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotSetKeepAspectRatio(bool))); connect(flipXButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipX())); connect(flipYButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipY())); connect(rotateCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCW())); connect(rotateCCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCCW())); // toggle visibility of different free buttons connect(freeMoveRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeRotationRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeScaleRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeShearRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); // only first group for free transform rotationGroup->hide(); moveGroup->show(); scaleGroup->hide(); shearGroup->hide(); // Init Warp Transform Values alphaBox->setSingleStep(0.1); alphaBox->setRange(0, 10, 1); connect(alphaBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetWarpAlpha(qreal))); connect(densityBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetWarpDensity(int))); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpDefaultPointsButtonClicked(bool))); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpCustomPointsButtonClicked(bool))); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpLockPointsButtonClicked())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpResetPointsButtonClicked())); // Init Cage Transform Values cageTransformButtonGroup->setId(cageAddEditRadio, 0); // we need to set manually since Qt Designer generates negative by default cageTransformButtonGroup->setId(cageDeformRadio, 1); connect(cageTransformButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotCageOptionsChanged(int))); // Init Liquify Transform Values liquifySizeSlider->setRange(KisLiquifyProperties::minSize(), KisLiquifyProperties::maxSize(), 2); liquifySizeSlider->setExponentRatio(4); liquifySizeSlider->setValue(60.0); connect(liquifySizeSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySizeChanged(qreal))); liquifySizeSlider->setToolTip(i18nc("@info:tooltip", "Size of the deformation brush")); liquifyAmountSlider->setRange(0.0, 1.0, 2); liquifyAmountSlider->setValue(0.05); connect(liquifyAmountSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyAmountChanged(qreal))); liquifyAmountSlider->setToolTip(i18nc("@info:tooltip", "Amount of the deformation you get")); liquifyFlowSlider->setRange(0.0, 1.0, 2); liquifyFlowSlider->setValue(1.0); connect(liquifyFlowSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyFlowChanged(qreal))); liquifyFlowSlider->setToolTip(i18nc("@info:tooltip", "When in non-buildup mode, shows how fast the deformation limit is reached.")); buidupModeComboBox->setCurrentIndex(0); // set to build-up mode by default connect(buidupModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(liquifyBuildUpChanged(int))); buidupModeComboBox->setToolTip("

" + i18nc("@info:tooltip", "Switch between Build Up and Wash mode of painting. Build Up mode adds deformations one on top of the other without any limits. Wash mode gradually deforms the piece to the selected deformation level.") + "

"); liquifySpacingSlider->setRange(0.0, 3.0, 2); liquifySizeSlider->setExponentRatio(3); liquifySpacingSlider->setSingleStep(0.01); liquifySpacingSlider->setValue(0.2); connect(liquifySpacingSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySpacingChanged(qreal))); liquifySpacingSlider->setToolTip(i18nc("@info:tooltip", "Space between two sequential applications of the deformation")); liquifySizePressureBox->setChecked(true); connect(liquifySizePressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifySizePressureChanged(bool))); liquifySizePressureBox->setToolTip(i18nc("@info:tooltip", "Scale Size value according to current stylus pressure")); liquifyAmountPressureBox->setChecked(true); connect(liquifyAmountPressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifyAmountPressureChanged(bool))); liquifyAmountPressureBox->setToolTip(i18nc("@info:tooltip", "Scale Amount value according to current stylus pressure")); liquifyReverseDirectionChk->setChecked(false); connect(liquifyReverseDirectionChk, SIGNAL(toggled(bool)), this, SLOT(liquifyReverseDirectionChanged(bool))); liquifyReverseDirectionChk->setToolTip(i18nc("@info:tooltip", "Reverse direction of the current deformation tool")); KisSignalMapper *liquifyModeMapper = new KisSignalMapper(this); connect(liquifyMove, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyScale, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyRotate, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyOffset, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyUndo, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); liquifyModeMapper->setMapping(liquifyMove, (int)KisLiquifyProperties::MOVE); liquifyModeMapper->setMapping(liquifyScale, (int)KisLiquifyProperties::SCALE); liquifyModeMapper->setMapping(liquifyRotate, (int)KisLiquifyProperties::ROTATE); liquifyModeMapper->setMapping(liquifyOffset, (int)KisLiquifyProperties::OFFSET); liquifyModeMapper->setMapping(liquifyUndo, (int)KisLiquifyProperties::UNDO); connect(liquifyModeMapper, SIGNAL(mapped(int)), SLOT(slotLiquifyModeChanged(int))); liquifyMove->setToolTip(i18nc("@info:tooltip", "Move: drag the image along the brush stroke")); liquifyScale->setToolTip(i18nc("@info:tooltip", "Scale: grow/shrink image under cursor")); liquifyRotate->setToolTip(i18nc("@info:tooltip", "Rotate: twirl image under cursor")); liquifyOffset->setToolTip(i18nc("@info:tooltip", "Offset: shift the image to the right of the stroke direction")); liquifyUndo->setToolTip(i18nc("@info:tooltip", "Undo: erase actions of other tools")); // Connect all edit boxes to the Editing Finished signal connect(densityBox, SIGNAL(editingFinished()), this, SLOT(notifyEditingFinished())); // Connect other widget (not having editingFinished signal) to // the same slot. From Qt 4.6 onwards the sequence of the signal // delivery is definite. connect(cmbFilter, SIGNAL(activated(KoID)), this, SLOT(notifyEditingFinished())); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(notifyEditingFinished())); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(notifyEditingFinished())); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(notifyEditingFinished())); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); // Liquify // // liquify brush options do not affect the image directly and are not // saved to undo, so we don't emit notifyEditingFinished() for them // Connect Apply/Reset buttons connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(slotButtonBoxClicked(QAbstractButton*))); // Mode switch buttons connect(freeTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetFreeTransformModeButtonClicked(bool))); connect(warpButton, SIGNAL(clicked(bool)), this, SLOT(slotSetWarpModeButtonClicked(bool))); connect(cageButton, SIGNAL(clicked(bool)), this, SLOT(slotSetCageModeButtonClicked(bool))); connect(perspectiveTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetPerspectiveModeButtonClicked(bool))); connect(liquifyButton, SIGNAL(clicked(bool)), this, SLOT(slotSetLiquifyModeButtonClicked(bool))); tooBigLabelWidget->hide(); connect(canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection); slotUpdateIcons(); } void KisToolTransformConfigWidget::slotUpdateIcons() { freeTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_main")); warpButton->setIcon(KisIconUtils::loadIcon("transform_icons_warp")); cageButton->setIcon(KisIconUtils::loadIcon("transform_icons_cage")); perspectiveTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_perspective")); liquifyButton->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_main")); liquifyMove->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_move")); liquifyScale->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_resize")); liquifyRotate->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_rotate")); liquifyOffset->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_offset")); liquifyUndo->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_erase")); middleRightButton->setIcon(KisIconUtils::loadIcon("arrow-right")); topRightButton->setIcon(KisIconUtils::loadIcon("arrow-topright")); middleTopButton->setIcon(KisIconUtils::loadIcon("arrow-up")); topLeftButton->setIcon(KisIconUtils::loadIcon("arrow-topleft")); middleLeftButton->setIcon(KisIconUtils::loadIcon("arrow-left")); bottomLeftButton->setIcon(KisIconUtils::loadIcon("arrow-downleft")); middleBottomButton->setIcon(KisIconUtils::loadIcon("arrow-down")); bottomRightButton->setIcon(KisIconUtils::loadIcon("arrow-downright")); btnTransformAroundPivotPoint->setIcon(KisIconUtils::loadIcon("pivot-point")); // pressure icons liquifySizePressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); liquifyAmountPressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } double KisToolTransformConfigWidget::radianToDegree(double rad) { double piX2 = 2 * M_PI; if (rad < 0 || rad >= piX2) { rad = fmod(rad, piX2); if (rad < 0) { rad += piX2; } } return (rad * 360. / piX2); } double KisToolTransformConfigWidget::degreeToRadian(double degree) { if (degree < 0. || degree >= 360.) { degree = fmod(degree, 360.); if (degree < 0) degree += 360.; } return (degree * M_PI / 180.); } void KisToolTransformConfigWidget::updateLiquifyControls() { blockUiSlots(); ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); const bool useWashMode = props->useWashMode(); liquifySizeSlider->setValue(props->size()); liquifyAmountSlider->setValue(props->amount()); liquifyFlowSlider->setValue(props->flow()); buidupModeComboBox->setCurrentIndex(useWashMode); liquifySpacingSlider->setValue(props->spacing()); liquifySizePressureBox->setChecked(props->sizeHasPressure()); liquifyAmountPressureBox->setChecked(props->amountHasPressure()); liquifyReverseDirectionChk->setChecked(props->reverseDirection()); KisLiquifyProperties::LiquifyMode mode = static_cast(props->mode()); bool canInverseDirection = mode != KisLiquifyProperties::UNDO; bool canUseWashMode = mode != KisLiquifyProperties::UNDO; liquifyReverseDirectionChk->setEnabled(canInverseDirection); liquifyFlowSlider->setEnabled(canUseWashMode && useWashMode); buidupModeComboBox->setEnabled(canUseWashMode); const qreal maxAmount = canUseWashMode ? 5.0 : 1.0; liquifyAmountSlider->setRange(0.0, maxAmount, 2); unblockUiSlots(); } void KisToolTransformConfigWidget::slotLiquifyModeChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); KisLiquifyProperties::LiquifyMode mode = static_cast(value); if (mode == props->mode()) return; props->setMode(mode); props->loadMode(); updateLiquifyControls(); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizeChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSize(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmount(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyFlowChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setFlow(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyBuildUpChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setUseWashMode(value); // 0 == build up mode / 1 == wash mode notifyConfigChanged(); // we need to enable/disable flow slider updateLiquifyControls(); } void KisToolTransformConfigWidget::liquifySpacingChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSpacing(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizePressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSizeHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountPressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmountHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyReverseDirectionChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setReverseDirection(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config) { blockUiSlots(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM || config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { quickTransformGroup->setEnabled(config.mode() == ToolTransformArgs::FREE_TRANSFORM); stackedWidget->setCurrentIndex(0); bool freeTransformIsActive = config.mode() == ToolTransformArgs::FREE_TRANSFORM; if (freeTransformIsActive) { freeTransformButton->setChecked(true); } else { perspectiveTransformButton->setChecked(true); } aXBox->setEnabled(freeTransformIsActive); aYBox->setEnabled(freeTransformIsActive); aZBox->setEnabled(freeTransformIsActive); freeRotationRadioButton->setEnabled(freeTransformIsActive); scaleXBox->setValue(config.scaleX() * 100.); scaleYBox->setValue(config.scaleY() * 100.); - shearXBox->setValue(config.shearX()); - shearYBox->setValue(config.shearY()); + shearXBox->setValue(config.shearX() * 100.); + shearYBox->setValue(config.shearY() * 100.); const QPointF anchorPoint = config.originalCenter() + config.rotationCenterOffset(); const KisTransformUtils::MatricesPack m(config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); translateXBox->setValue(anchorPointView.x()); translateYBox->setValue(anchorPointView.y()); aXBox->setValue(radianToDegree(config.aX())); aYBox->setValue(radianToDegree(config.aY())); aZBox->setValue(radianToDegree(config.aZ())); aspectButton->setKeepAspectRatio(config.keepAspectRatio()); cmbFilter->setCurrent(config.filterId()); QPointF pt = m_transaction->currentConfig()->rotationCenterOffset(); pt.rx() /= m_transaction->originalHalfWidth(); pt.ry() /= m_transaction->originalHalfHeight(); for (int i = 0; i < 9; i++) { if (qFuzzyCompare(m_handleDir[i].x(), pt.x()) && qFuzzyCompare(m_handleDir[i].y(), pt.y())) { m_rotationCenterButtons->button(i)->setChecked(true); break; } } btnTransformAroundPivotPoint->setChecked(config.transformAroundRotationCenter()); } else if (config.mode() == ToolTransformArgs::WARP) { stackedWidget->setCurrentIndex(1); warpButton->setChecked(true); if (config.defaultPoints()) { densityBox->setValue(std::sqrt(config.numPoints())); } cmbWarpType->setCurrentIndex((int)config.warpType()); defaultRadioButton->setChecked(config.defaultPoints()); customRadioButton->setChecked(!config.defaultPoints()); densityBox->setEnabled(config.defaultPoints()); customWarpWidget->setEnabled(!config.defaultPoints()); updateLockPointsButtonCaption(); } else if (config.mode() == ToolTransformArgs::CAGE) { // default UI options resetUIOptions(); // we need at least 3 point before we can start actively deforming if (config.origPoints().size() >= 3) { cageTransformDirections->setText(i18n("Switch between editing and deforming cage")); cageAddEditRadio->setVisible(true); cageDeformRadio->setVisible(true); // update to correct radio button if (config.isEditingTransformPoints()) cageAddEditRadio->setChecked(true); else cageDeformRadio->setChecked(true); changeGranularity->setCurrentIndex(log2(config.pixelPrecision()) - 2); granularityPreview->setCurrentIndex(log2(config.previewPixelPrecision()) - 2); } } else if (config.mode() == ToolTransformArgs::LIQUIFY) { stackedWidget->setCurrentIndex(3); liquifyButton->setChecked(true); const KisLiquifyProperties *props = config.liquifyProperties(); switch (props->mode()) { case KisLiquifyProperties::MOVE: liquifyMove->setChecked(true); break; case KisLiquifyProperties::SCALE: liquifyScale->setChecked(true); break; case KisLiquifyProperties::ROTATE: liquifyRotate->setChecked(true); break; case KisLiquifyProperties::OFFSET: liquifyOffset->setChecked(true); break; case KisLiquifyProperties::UNDO: liquifyUndo->setChecked(true); break; case KisLiquifyProperties::N_MODES: qFatal("Unsupported mode"); } updateLiquifyControls(); } unblockUiSlots(); } void KisToolTransformConfigWidget::updateLockPointsButtonCaption() { ToolTransformArgs *config = m_transaction->currentConfig(); if (config->isEditingTransformPoints()) { lockUnlockPointsButton->setText(i18n("Lock Points")); } else { lockUnlockPointsButton->setText(i18n("Unlock Points")); } } void KisToolTransformConfigWidget::setApplyResetDisabled(bool disabled) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); Q_ASSERT(applyButton); Q_ASSERT(resetButton); applyButton->setDisabled(disabled); resetButton->setDisabled(disabled); } void KisToolTransformConfigWidget::resetRotationCenterButtons() { int checkedId = m_rotationCenterButtons->checkedId(); if (checkedId >= 0 && checkedId <= 8) { // uncheck the current checked button m_rotationCenterButtons->button(9)->setChecked(true); } } bool KisToolTransformConfigWidget::workRecursively() const { return chkWorkRecursively->isChecked(); } void KisToolTransformConfigWidget::setTooBigLabelVisible(bool value) { tooBigLabelWidget->setVisible(value); } void KisToolTransformConfigWidget::blockNotifications() { m_notificationsBlocked++; } void KisToolTransformConfigWidget::unblockNotifications() { m_notificationsBlocked--; } void KisToolTransformConfigWidget::notifyConfigChanged() { if (!m_notificationsBlocked) { emit sigConfigChanged(); } m_configChanged = true; } void KisToolTransformConfigWidget::blockUiSlots() { m_uiSlotsBlocked++; } void KisToolTransformConfigWidget::unblockUiSlots() { m_uiSlotsBlocked--; } void KisToolTransformConfigWidget::notifyEditingFinished() { if (m_uiSlotsBlocked || m_notificationsBlocked || !m_configChanged) return; emit sigEditingFinished(); m_configChanged = false; } void KisToolTransformConfigWidget::resetUIOptions() { // reset tool states since we are done (probably can encapsulate this later) ToolTransformArgs *config = m_transaction->currentConfig(); if (config->mode() == ToolTransformArgs::CAGE) { cageAddEditRadio->setVisible(false); cageAddEditRadio->setChecked(true); cageDeformRadio->setVisible(false); cageTransformDirections->setText(i18n("Create 3 points on the canvas to begin")); // ensure we are on the right options view stackedWidget->setCurrentIndex(2); } } void KisToolTransformConfigWidget::slotButtonBoxClicked(QAbstractButton *button) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); resetUIOptions(); if (button == applyButton) { emit sigApplyTransform(); } else if (button == resetButton) { emit sigResetTransform(); } } void KisToolTransformConfigWidget::slotSetFreeTransformModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(freeTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::FREE_TRANSFORM); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetWarpModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(warpButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::WARP); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetCageModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(cageButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::CAGE); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetLiquifyModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(liquifyButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::LIQUIFY); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetPerspectiveModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(perspectiveTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::PERSPECTIVE_4POINT); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotFilterChanged(const KoID &filterId) { ToolTransformArgs *config = m_transaction->currentConfig(); config->setFilterId(filterId.id()); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotRotationCenterChanged(int index) { if (m_uiSlotsBlocked) return; if (index >= 0 && index <= 8) { ToolTransformArgs *config = m_transaction->currentConfig(); double i = m_handleDir[index].x(); double j = m_handleDir[index].y(); config->setRotationCenterOffset(QPointF(i * m_transaction->originalHalfWidth(), j * m_transaction->originalHalfHeight())); notifyConfigChanged(); updateConfig(*config); } } void KisToolTransformConfigWidget::slotTransformAroundRotationCenter(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setTransformAroundRotationCenter(value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(value / 100.); } if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int( value/ m_scaleRatio ); scaleYBox->blockSignals(true); scaleYBox->setValue(calculatedValue); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(calculatedValue / 100.); } scaleYBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(value / 100.); } if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int(m_scaleRatio * value); scaleXBox->blockSignals(true); scaleXBox->setValue(calculatedValue); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(calculatedValue / 100.); } scaleXBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); - config->setShearX((double)value); + config->setShearX((double)value / 100.); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); - config->setShearY((double)value); + config->setShearY((double)value / 100.); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetTranslateX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); const QPointF newAnchorPointView(value, anchorPointView.y()); config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView); translateXBox->setValue(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetTranslateY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); const QPointF newAnchorPointView(anchorPointView.x(), value); config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView); translateYBox->setValue(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetAX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAX(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAY(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAZ(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotFlipX() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(config->scaleX() * -1); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotFlipY() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(config->scaleY() * -1); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotRotateCW() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(normalizeAngle(config->aZ() + M_PI_2)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotRotateCCW() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(normalizeAngle(config->aZ() - M_PI_2)); } notifyConfigChanged(); notifyEditingFinished(); } // change free transform setting we want to alter (all radio buttons call this) void KisToolTransformConfigWidget::slotTransformAreaVisible(bool value) { Q_UNUSED(value); //QCheckBox sender = (QCheckBox)(*)(QObject::sender()); QString senderName = QObject::sender()->objectName(); // only show setting with what we have selected rotationGroup->hide(); shearGroup->hide(); scaleGroup->hide(); moveGroup->hide(); if ("freeMoveRadioButton" == senderName) { moveGroup->show(); } else if ("freeShearRadioButton" == senderName) { shearGroup->show(); } else if ("freeScaleRadioButton" == senderName) { scaleGroup->show(); } else { rotationGroup->show(); } } void KisToolTransformConfigWidget::slotSetKeepAspectRatio(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setKeepAspectRatio(value); if (value) { blockNotifications(); int tmpXScaleBox = scaleXBox->value(); int tmpYScaleBox = scaleYBox->value(); m_scaleRatio = (tmpXScaleBox / (double) tmpYScaleBox); unblockNotifications(); } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetWarpAlpha(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setAlpha((double)value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetWarpDensity(int value) { if (m_uiSlotsBlocked) return; setDefaultWarpPoints(value); } void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine) { ToolTransformArgs *config = m_transaction->currentConfig(); KisTransformUtils::setDefaultWarpPoints(pointsPerLine, m_transaction, config); notifyConfigChanged(); } void KisToolTransformConfigWidget::activateCustomWarpPoints(bool enabled) { ToolTransformArgs *config = m_transaction->currentConfig(); densityBox->setEnabled(!enabled); customWarpWidget->setEnabled(enabled); if (!enabled) { config->setEditingTransformPoints(false); setDefaultWarpPoints(densityBox->value()); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID); } else { config->setEditingTransformPoints(true); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::DRAW); setDefaultWarpPoints(0); } updateLockPointsButtonCaption(); } void KisToolTransformConfigWidget::slotWarpDefaultPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(!value); } void KisToolTransformConfigWidget::slotWarpCustomPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(value); } void KisToolTransformConfigWidget::slotWarpResetPointsButtonClicked() { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(true); } void KisToolTransformConfigWidget::slotWarpLockPointsButtonClicked() { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setEditingTransformPoints(!config->isEditingTransformPoints()); if (config->isEditingTransformPoints()) { // reinit the transf points to their original value ToolTransformArgs *config = m_transaction->currentConfig(); int nbPoints = config->origPoints().size(); for (int i = 0; i < nbPoints; ++i) { config->transfPoint(i) = config->origPoint(i); } } updateLockPointsButtonCaption(); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotWarpTypeChanged(int index) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); switch (index) { case KisWarpTransformWorker::AFFINE_TRANSFORM: case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: case KisWarpTransformWorker::RIGID_TRANSFORM: config->setWarpType((KisWarpTransformWorker::WarpType)index); break; default: config->setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotCageOptionsChanged(int value) { if ( value == 0) { slotEditCagePoints(true); } else { slotEditCagePoints(false); } notifyEditingFinished(); } void KisToolTransformConfigWidget::slotEditCagePoints(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->refTransformedPoints() = config->origPoints(); config->setEditingTransformPoints(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotGranularityChanged(QString value) { if (m_uiSlotsBlocked) return; KIS_SAFE_ASSERT_RECOVER_RETURN(value.toInt() > 1); ToolTransformArgs *config = m_transaction->currentConfig(); config->setPixelPrecision(value.toInt()); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotPreviewGranularityChanged(QString value) { if (m_uiSlotsBlocked) return; KIS_SAFE_ASSERT_RECOVER_RETURN(value.toInt() > 1); ToolTransformArgs *config = m_transaction->currentConfig(); config->setPreviewPixelPrecision(value.toInt()); notifyConfigChanged(); }